{ garconv (Garmin-Converter)                                            V 2.5
  Funktion: Konvertiert Dateien zwischen den Anwendungen OZIExplorer und
  Garmin Training Center sowie SwissMap (xol) ins Training Center.
  D.h. im OZIExplorer als Track abgespeicherte *.plt Dateien können ins
  .crs-Format umgewandelt und als Strecke ins Training Center eingelesen werden
  bzw. umgekehrt können .hst Dateien aus dem Training Center ins .plt-Format
  für den OZIExplorer konvertiert werden.
  EINSCHRÄNKUNGEN:
  -Höhenangaben werden generell ignoriert.
  -als Zeit bzw. Geschwindigkeit für die .crs-Datei wird der Startpunkt mit der
   aktuellen Zeit und alle folgenden Trackpunkte jeweils eine Sekunde später als
   der vorherige Punkt versehen.
  -die .hst-Datei im XML-Format benötigt die wesentlichen XML-Tags jeweils als
   ein Tag pro Zeile
  Es werden praktisch keine Überprüfungen der eingelesenen Daten durchgeführt &
  alle Formate wurde mittels Try and Error erforscht.
  Erstellt: 05.05.2006 Opp
  Kompiliert mittels Lazarus 0.9.10 vom 02.10.2005
  Anpassungen:
  - 19.05.06: upgrade auf v1.1: Konvertierung .wpt nach .crs eingefügt
      procedure TMainForm.convwpt2crs erstellt,
      procedure TMainForm.InputFileChange um Erkennung .wpt erweitert
      procedure TMainForm.crsoutFileChange & procedure TMainForm.convertClick
      angepasst
  - 22.05.06: upgrade auf v1.2: Outputfile-Dialog eingebaut und diverse
      Sicherheitsabfragen angepasst
  - 08.06.06: upgrade auf v1.3: Konvertierung .xol nach .crs eingefügt
      proc TMainForm.InputFileChange um Erkennung .wpt erweitert
      proc TMainForm.convertClick angepasst
      proc TMainForm.convxol2crs erstellt
  - 02.11.06: upgrade auf v2.0: Konvertierung .xml nach .crs eingefügt
      Konvertierung .xol nach .crs und .wpt nach .crs gelöscht weil überflüssig!
      Interne Programmstrucktur angepasst: Daten werden neu in ein Array ein-
      gelesen, damit in Abhängigkeit der Steigung die Geschwindigkeit und daraus
      die benötigte Zeit berechnet werden kann.
      Dadurch müssen Import- und Export-funktionen nur noch je einmal geschrieben
      werden.
      Die .xml-Datei aus SwissMap kann so wie sie ist verwendet werden. Als
      Trackname wird der Dateiname vorgegeben.
      Für die .plt-Datei sollte im OziExplorer den markanten Punkten eine Höhe
      zugewiesen werden. Mindestens aber die Höhe der Start- und Endkoordinate!
  ab V2.1 kompilier mittels Lazarus 0.9.24 vom 14.11.2007
  - 21.03.08: upgrade auf v2.1: neues TraningCenter History-Format .tcx kann nun
      auch eingelesen werden (nur proc InputFileChange, TrackListClick und
      Dispatcher convertClick mussten angepasst werden) zusätzlich wird das
      TwixRoute 37 .csv-Format erkannt und eingelesen. Leider sind die TwixRoute-
      Daten jedoch ziemlich ungenügend für eine GPS-Nachfahrt!
  - 20.08.08: upgrade auf v2.2: Base Speed, Initial slow down time und Reduced
      Speed werden aus garconv.ini eingelesen, im Programmfenster angezeigt und
      entsprechend in der Procedur expcrs umgesetzt.
  - 07.01.09: upgrade auf v2.3: neuer Importfilter für .gpx-Dateien von google
      mittels GMapToGPX von http://www.elsewhere.org/journal/gmaptogpx/ erstellt.
  - 05.02.09: upgrade auf v2.4: Importfilter für aus TraningCenter exportierte
      Course als .tcx datei erstellt.
  - 22.08.09: upgrade auf v2.5: doppelt vorkommende Punkte können nun ignoriert
      werden (statt nur eine Fehlermeldung auszugeben und abzubrechen).
      Steigung auf max 20% begrentzt & Formel zur Geschwindigkeitsberechnung an-
      gepasst (siehe expcrs).
      BaseSpeed kann nun auch im Programm angepasst werden.
 ***************************************************************************
 *                                                                         *
 *   This source 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 code 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.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.        *
 *                                                                         *
 ***************************************************************************
}
unit frmmain;

{$MODE Delphi}

interface

uses
  LCLIntf, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,
  About, ComCtrls, ActnList, Menus, LResources, Buttons, strutils, dateutils,
  math, FileUtil;

type
  { TMainForm }
  TMainForm = class(TForm)
    InFTyp: TComboBox;
    exit_btn: TButton;
    BaseSpeedText: TLabel;
    BaseSpeedInp: TEdit;
    Label9: TLabel;
    RedSpeedDurationText: TLabel;
    OutFTyp: TComboBox;
    Label5: TLabel;
    Label6: TLabel;
    Help: TMenuItem;
    Label7: TLabel;
    MenuItem2: TMenuItem;
    OutFDlg: TOpenDialog;
    OutPutFile: TEdit;
    Label8: TLabel;
    ReducedSpeedText: TLabel;
    selOutpFile: TButton;
    TrackList: TListBox;
    OutFileDlgOutputFile: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    TrkName: TEdit;
    convert: TButton;
    InputFile: TEdit;
    selInpFile: TButton;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    MIOpen: TMenuItem;
    N1: TMenuItem;
    MIQuit: TMenuItem;
    InpFDlg: TOpenDialog;
    Nrofpoints: TEdit;
    procedure BaseSpeedInpChange(Sender: TObject);
    procedure BaseSpeedInpEditingDone(Sender: TObject);
    procedure InpFOpenExec(Sender: TObject);
    procedure OutFOpenExec(Sender: TObject);
    procedure InputFileChange(Sender: TObject);
    procedure MenuItem2Click(Sender: TObject);
    procedure OutFTypChange(Sender: TObject);
    procedure StartPrg(Sender: TObject);
    procedure TrackListClick(Sender: TObject);
    procedure TrkNameEditingDone(Sender: TObject);
    procedure convertClick(Sender: TObject);
    procedure exit_btnClick(Sender: TObject);
    procedure In_xml;
    procedure In_plt;
    procedure In_hst;
    procedure In_csv;
    procedure In_gpx;
    procedure expcrs;
    procedure expplt;
    procedure expxol;
  private
    { Private declarations }
  public
    { Public declarations }
    function real2str(r: real): String;
  end;

type TPoint = record
    N: real;	// Nord-Koordinate
    E: real;	// Ost-Koordinate
    h: Integer;	// Höhe
    t: real;	// Zeit die nötig ist um vom Start diesen Punkt zu erreichen
    d: real;	// Distanz vom vorhergehenden Punkt bis zu diesem Punkt
  end;

var
  MainForm: TMainForm;
  InF, OutF, IniF: tstringlist;
  PtList: ARRAY of TPoint;
  nrpts: Integer;
// BaseSpeed in [km/h], RedSpeedDuration in [s], ReducedSpeed in [%]
  BaseSpeed, RedSpeedDuration, ReducedSpeed: Integer;
  trueBaseSpeed: Extended;

implementation

uses lcltype;

procedure TMainForm.StartPrg(Sender: TObject);
var
  i: integer;
begin
  BaseSpeed:=20;
  BaseSpeedInp.text:='20';
  RedSpeedDuration:= 120;
  ReducedSpeed:= 15;
  If FileExists('garconv.ini') Then
  begin
    IniF:=tstringlist.create;
    IniF.LoadFromFile('garconv.ini');
    for i:=0 to IniF.Count-1 do
    begin
      if CompareStr(leftstr(Trim(IniF[i]),9), 'BaseSpeed')=0 then
      begin
         BaseSpeedInp.Text:=ExtractDelimited(2, IniF[i], [' ']);
         BaseSpeed:=round(StrtoFloat(ExtractDelimited(2, IniF[i], [' '])));
      end;
    end;
    for i:=0 to IniF.Count-1 do
    begin
      if CompareStr(leftstr(Trim(IniF[i]),3), 'RSD')=0 then
         RedSpeedDuration:=round(StrtoFloat(ExtractDelimited(2, IniF[i], [' '])));
    end;
    for i:=0 to IniF.Count-1 do
    begin
      if CompareStr(leftstr(Trim(IniF[i]),12), 'ReducedSpeed')=0 then
         ReducedSpeed:=round(StrtoFloat(ExtractDelimited(2, IniF[i], [' '])));
    end;
  end;
  IniF.free;
  RedSpeedDurationText.Caption:='duration: '+InttoStr(RedSpeedDuration)+'s';
  ReducedSpeedText.Caption:='reduction: '+InttoStr(ReducedSpeed)+'%';
end;

procedure TMainForm.BaseSpeedInpChange(Sender: TObject);
begin

end;

procedure TMainForm.BaseSpeedInpEditingDone(Sender: TObject);
begin
  if StrtoInt(BaseSpeedInp.Text) > 60 then
  begin
    MessageDlg('the BaseSpeed must be below 60km/h - it will be set to 20km/h', mtError, [mbOk],1);
    BaseSpeedInp.Text:='20';
  end
  else
  begin
    BaseSpeed:=StrtoInt(BaseSpeedInp.Text);
  end;
end;

// öffnet den "Input-File öffnen"-Dialog und stellt sicher, dass nur 1 Datei
// ausgewählt wird.
procedure TMainForm.InpFOpenExec(Sender: TObject);

begin
  If InpFDlg.Execute then
    If InpFDlg.Files.Count > 1 then
    begin
      MessageDlg('you can''t select multiple files!', mtError, [mbOk],1);
    end
    else
    begin
      InputFile.Text:=InpFDlg.FileName;
    end;
end;

// öffnet den "Output-File öffnen"-Dialog und stellt sicher, dass nur 1 Datei
// ausgewählt wird.
procedure TMainForm.OutFOpenExec(Sender: TObject);

begin
  If OutFDlg.Execute then
    If OutFDlg.Files.Count > 1 then
    begin
      MessageDlg('you can''t select multiple files!', mtError, [mbOk],1);
    end
    else
    begin
      OutPutFile.Text:=OutFDlg.FileName;
    end;
end;

// versucht den Typ des ausgewählten Input-Files zu erkennen
procedure TMainForm.InputFileChange(Sender: TObject);
var
  i, nr: integer;
  str2add: widestring;
begin
  TrackList.Items.Clear; TrkName.Text:=' '; Nrofpoints.Text:=' ';
  InF:=tstringlist.create;
  if CompareStr(InputFile.Text, '')<>0 then
  begin
    InF.LoadFromFile(UTF8toansi(InputFile.Text));
    if CompareStr(ExtractFileExt(Inputfile.Text), '.xml')=0 then
      begin   // xml-File gefunden, no additional Infos in there
        InFTyp.ItemIndex:=0;
        nr:=0;
        for i:=0 to InF.Count-1 do
        begin
          if CompareStr(leftstr(Trim(InF[i]),8), '<Point3D')=0 then inc(nr);
        end;
        NrofPoints.Text:=InttoStr(nr);
        TrkName.Text:=LeftStr(ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.']),15);
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.crs';
        OutFTyp.ItemIndex:=0;
      end
    else
      if CompareStr(ExtractFileExt(Inputfile.Text), '.plt')=0 then
      begin   // plt-File gefunden, Trackname laden und File anhand Anzahl Punkte
              // in Zeile 5 validieren
        InFTyp.ItemIndex:=1;
        try
          nrpts:=strtoint(trim(InF[5]));
          Nrofpoints.Text:=Inttostr(nrpts);
        except
          MessageDlg('sorry, it seems this is not a valid .plt-file', mtError, [mbOk],1);
          Nrofpoints.Text:=' ';
          InputFile.Text:=' ';
        end;
        TrkName.Text:=LeftStr(Trim(ExtractDelimited(4, InF[4], [','])),15);
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.crs';
        OutFTyp.ItemIndex:=0;
      end
    else
      if CompareStr(ExtractFileExt(Inputfile.Text), '.hst')=0 then
      begin  // hst-File gefunden (Garmin Traning Center)
        InFTyp.ItemIndex:=2;
        for i:=1 to InF.Count-1 do
        begin
          if Trim(InF[i])='<Run>' then
          begin
            TrackList.Items.Add(ExtractDelimited(2, InF[i+1], ['"'])+' at Line #'+InttoStr(i+1));
          end;
        end;
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.xol';
        OutFTyp.ItemIndex:=2;
      end
    else
      if CompareStr(ExtractFileExt(Inputfile.Text), '.tcx')=0 then
      begin  // tcx-File gefunden (Garmin Traning Center V3.3.4)
        for i:=1 to InF.Count-1 do
        begin
          if Trim(InF[i])='<Activity Sport="Biking">' then  // für tcx-history dateien
          begin
            InFTyp.ItemIndex:=3;
            TrackList.Items.Add(ExtractDelimited(2, InF[i+2], ['"'])+' @ Line #'+InttoStr(i+2));
          end;
          if Trim(InF[i])='<Course>' then  // für tcx-course dateien
          begin
            InFTyp.ItemIndex:=4;
            str2add:=ExtractDelimited(2, InF[i+1], ['<']);
            str2add:=ExtractDelimited(2, str2add, ['>'])+' @ Line #'+InttoStr(i+1);
            TrackList.Items.Add(str2add);
          end;
        end;
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.xol';
        OutFTyp.ItemIndex:=2;
      end
    else
      if CompareStr(ExtractFileExt(Inputfile.Text), '.csv')=0 then
      begin  // csv-File gefunden (TwixRoute 37)
        InFTyp.ItemIndex:=5;
          nrpts:=InF.Count;
          Nrofpoints.Text:=Inttostr(nrpts);
        TrkName.Text:=LeftStr(ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.']),15);
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.crs';
        OutFTyp.ItemIndex:=0;
      end
    else
      if CompareStr(ExtractFileExt(Inputfile.Text), '.gpx')=0 then
      begin   // gpx-File gefunden, no additional Infos in there
        InFTyp.ItemIndex:=6;
        nr:=0;
        for i:=0 to InF.Count-1 do
        begin
          if CompareStr(leftstr(Trim(InF[i]),6), '<trkpt')=0 then inc(nr);
        end;
        NrofPoints.Text:=InttoStr(nr);
        TrkName.Text:=LeftStr(ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.']),15);
        OutPutFile.Text:=ExtractFilePath(InputFile.Text)+ExtractDelimited(1,ExtractFileName(Inputfile.Text),['.'])+'.crs';
        OutFTyp.ItemIndex:=0;
      end
    else
    begin
      OutPutFile.Text:=' ';
      InFTyp.ItemIndex:=-1;
      messagedlg('Can''t detect the type of input-file! Make shure it is a valid .xml, .plt, .hst, .tcx or .xol file and select the type of conversion', mtWarning, [mbOk], 1);
      exit;
    end;
  end;
end;

// Ruft das About-Dialog-Fenster auf
procedure TMainForm.MenuItem2Click(Sender: TObject);
begin
  AboutWin.ShowModal;
end;

// Ändert das Ausgabefile auf die gewählte Konvertierungsausgabe-Endung
procedure TMainForm.OutFTypChange(Sender: TObject);
var
  ext: string;
begin
  if MessageDlg('Shall I change the extension of the output-filename?', mtConfirmation,mbYesNo, 1)=6 then
  begin
    case OutFTyp.ItemIndex of
     0 : ext:='.crs';
     1 : ext:='.plt';
     2, 3 : ext:='.xol';
    end;
    OutPutFile.Text:=ExtractDelimited(1,OutPutFile.Text,['.'])+ext;
  end;
end;

// Erstellt den Tracknamen aus dem Aufzeichnungsdatum des ausgewählten Tracks
procedure TMainForm.TrackListClick(Sender: TObject);
var
  i, index: Integer;
  datzeit, dat, zeit, dd, mm, yy, tag: string;
  endtrack: Boolean;
begin
  if TrackList.Items.Count > 0 then
  begin
    index:=TrackList.ItemIndex;
    i:=StrtoInt(ExtractDelimited(2, Tracklist.Items[index], ['#']));
    if InFTyp.ItemIndex=3 then
    begin
      datzeit:=ExtractDelimited(2,InF[i],['"']);
      dat:=ExtractDelimited(1,datzeit,['T']);
      dd:=ExtractDelimited(3,dat,['-'])+'-';
      mm:=ExtractDelimited(2,dat,['-'])+'-';
      yy:=RightStr(ExtractDelimited(1,dat,['-']),2);
      zeit:=LeftStr(ExtractDelimited(2,datzeit,['T']),5);
      TrkName.Text:=dd+mm+yy+'/'+zeit;
    end
    else
    begin
      tag:=ExtractDelimited(1, Tracklist.Items[index], ['@']);
      tag:=leftstr(tag,length(tag)-1);
      TrkName.Text:=tag;
    end;
    // nun zählen wir noch die Anz. Punkte in diesem Track
    endtrack:=False;
    index:=0;
    Repeat
      tag:=Trim(ExtractDelimited(1, InF[i], ['>']));
      if CompareStr(Tag, '</Activity')=0 then endtrack:=True;  // in .tcx-history dateien
      if CompareStr(Tag, '</Run')=0 then endtrack:=True;       // in .hst dateien
      if CompareStr(Tag, '</Course')=0 then endtrack:=True;    // in .tcx-course dateien
      if CompareStr(Tag, '</Position')=0 then inc(index);
      inc(i);
    until endtrack;
    Nrofpoints.Text:=Inttostr(index);
  end;
end;

// stellt sicher, dass der Trackname max. 15 Zeichen lang ist
procedure TMainForm.TrkNameEditingDone(Sender: TObject);
begin
  if length(TrkName.Text)>15 then
  begin
    TrkName.Text:=LeftStr(TrkName.Text, 15);
    MessageDlg('Sorry, max. 15 characters allowed', mtError, [mbOk],1);
  end;
end;

// Funktion zum Schliessen des Prg, verwendet im Button und Menü
procedure TMainForm.exit_btnClick(Sender: TObject);
begin
  Close;
end;

// Importfilter für xml-Dateien
procedure TMainForm.In_xml;

  // rechnet ch1903 in wgs84-Koord um
  procedure ch2wgs(xs, ys: real; n: Integer);

  var
    x1, y1: real;
  begin
    x1:=(xs-200000)/1000000;
    y1:=(ys-600000)/1000000;
    PtList[n].N:=(16.9023892+3.238272*x1-0.270978*sqr(y1)-0.002528*sqr(x1)-0.0447*sqr(y1)*x1-0.014*y1*y1*y1)*100/36;
    PtList[n].E:=(2.6779094+4.728982*y1+0.791484*y1*x1+0.1306*y1*sqr(x1)-0.0436*y1*y1*y1)*100/36;
  end;

  // Berechnet die Distanz zw. 2 Punkten in ch1903-Koordinaten (da ch1903-Koord.
  // in Metern genügt der Phytagoras zur Entfernungsberechnung)
  function entfernung(x1, y1, x2, y2: real): real;
  begin
    entfernung:=sqrt(sqr(x2-x1)+sqr(y2-y1));
  end;

var i, j, dh: Integer;

begin
  j:=0;
  PtList[0].d:=0;
  PtList[0].t:=0;
  for i:=0 to InF.Count-1 do
  begin
    if CompareStr(leftstr(Trim(InF[i]),8), '<Point3D')=0 then
    begin
      // erstmal werden alle gegebenen Parameter eingelesen
      PtList[j].N:=StrtoFloat(ExtractDelimited(4, InF[i], ['"']));
      PtList[j].E:=StrtoFloat(ExtractDelimited(2, InF[i], ['"']));
      PtList[j].h:=round(StrtoFloat(ExtractDelimited(6, InF[i], ['"']))/10);

      // Aus den N & E-Koordinaten berechnen wir die Distanz zw. 2 Punkten
      PtList[j].d:=entfernung(PtList[j-1].N, PtList[j-1].E, PtList[j].N,PtList[j].E);
      dh:=PtList[j].h-PtList[j-1].h;

      // Bei zwei Punkte an demselben Ort muss der zweite Punkt ignoriert werden
      // (ansonsten erfolgt der Abbruch da sonst später (beim Export) eine Division
      // durch 0 erfolgt -> runtime-ERROR
      if (dh=0) AND (PtList[j].d=0) then
      begin
        if MessageDlg('Point Nr.: '+Inttostr(j)+' has the same coordinates like the one bevor - I will skip that point!', mtError, [mbOk, mbCancel],1)=mrCancel then halt;
      end
      else
      begin
        ch2wgs(PtList[j-1].N, PtList[j-1].E, j-1);
        inc(j);
      end;
    end;
  end;
  // auch den letzten Punkt noch umrechnen und das PtList-Array auf die aktuelle Anz. Punkte anpassen
  ch2wgs(PtList[j-1].N, PtList[j-1].E, j-1);
  SetLength(PtList, j);

{//die folgenden Zeilen dienen als Info wie getestet werden kann
  MessageDlg('This is the End - j='+inttostr(j), mtError, [mbOk],1);
  for j:=1 to high(PtList) do
  begin
    WriteLn(inttostr(j)+' - '+real2str(PtList[j].N)+' - '+real2str(PtList[j].E)+' - '+real2str(PtList[j].d)+' - '+real2str(PtList[j].h));
  end;
  MessageDlg('This is the End1', mtError, [mbOk],1);
}
end;

// Importfilter für .plt-Dateien
procedure TMainForm.In_plt;

  // Entfernungsberechnung zweier Punkte in ch1903-Koordinaten
  function entfernung(x1, y1, x2, y2: real): real;
    function rad(grad: real): real;
    begin
      rad:=grad/180*Pi();
    end;
  var
    br1, ln1, br2, ln2: real;
  begin
    br1:=rad(x1); ln1:=rad(y1);
    br2:=rad(x2); ln2:=rad(y2);
    entfernung:=6378137*ArcCos(Sin(br1)*Sin(br2)+cos(br1)*cos(br2)*cos(ln2-ln1));
  end;

var i, j, h: Integer;
    dh: real;

begin
  // gegebene Parameter (N & E-Koord und Höhe) einlesen
  for i:= 6 to 5+nrpts do
  begin
    PtList[i-6].N:=StrtoFloat(Trim(ExtractDelimited(1, InF[i], [','])));
    PtList[i-6].E:=StrtoFloat(Trim(ExtractDelimited(2, InF[i], [','])));
    h:=round(StrtoFloat(ExtractDelimited(4, InF[i], [',']))/3.28);
    if h < 0 then h:=-999;
    PtList[i-6].h:=h;
  end;
  h:=0;
  // Entfernung berechnen und die fehlenden Höhen linear ergänzen
  for i:=1 to high(PtList) do
  begin
    PtList[i].d:=entfernung(PtList[i-1].N, PtList[i-1].E, PtList[i].N,PtList[i].E);
    if PtList[i].h<>-999 then
    begin
      dh:=(PtList[i].h-PtList[h].h)/(i-h);
      for j:=h+1 to i-1 do PtList[j].h:=round(PtList[h].h+(j-h)*dh);
      h:=i;
    end;
  end;
end;

// Importfilter für .hst, .tcx-history sowie .tcx-course-Dateien
procedure TMainForm.In_hst;

  // gibt den Wert zwischen zwei XML-Tags zurück
  Function getValue(line: String): real;
  begin
    getValue:=StrtoFloat(ExtractDelimited(1,ExtractDelimited(2, line, ['>']),['<']));
  end;

var
  index, i, j, nrpts: Integer;
  Tag: string;

begin
  index:=TrackList.ItemIndex;
  if index=-1 then
  begin
    MessageDlg('Please select the Track you want to convert', mtError, [mbOk], 1);
    exit;
  end;
  i:=StrtoInt(ExtractDelimited(2, Tracklist.Items[index], ['#']));
  nrpts:=strtoint(Nrofpoints.Text);
  j:=0;
  repeat
    Tag:=Trim(ExtractDelimited(1, InF[i], ['>']));
    if CompareStr(Tag,'<LatitudeDegrees')=0 then PtList[j].N:=getValue(InF[i]);
    if CompareStr(Tag,'<LongitudeDegrees')=0 then PtList[j].E:=getValue(InF[i]);
    if CompareStr(Tag,'</Position')=0 then
    begin
      PtList[j].h:=round(getValue(InF[i+1]));
      inc(j);
    end;
    inc(i);
  until j=nrpts;
end;

// Importfilter für .csv-Dateien
procedure TMainForm.In_csv;

  // Entfernungsberechnung zweier Punkte in ch1903-Koordinaten
  function entfernung(x1, y1, x2, y2: real): real;
    function rad(grad: real): real;
    begin
      rad:=grad/180*Pi();
    end;
  var
    br1, ln1, br2, ln2: real;
  begin
    br1:=rad(x1); ln1:=rad(y1);
    br2:=rad(x2); ln2:=rad(y2);
    entfernung:=6378137*ArcCos(Sin(br1)*Sin(br2)+cos(br1)*cos(br2)*cos(ln2-ln1));
  end;

var i : Integer;

begin
  // gegebene Parameter (N & E-Koord und Höhe) einlesen
  for i:= 0 to nrpts-1 do
  begin
    PtList[i].E:=StrtoFloat(Trim(ExtractDelimited(1, InF[i], [','])));
    PtList[i].N:=StrtoFloat(Trim(ExtractDelimited(2, InF[i], [','])));
    PtList[i].h:=280;  // TwixRoute exportiert leider keine Höhen -> Std-Höhe
  end;
  for i:=1 to high(PtList) do
    PtList[i].d:=entfernung(PtList[i-1].N, PtList[i-1].E, PtList[i].N,PtList[i].E);
end;

// Importfilter für gpx-Dateien
procedure TMainForm.In_gpx;

  // Entfernungsberechnung zweier Punkte in ch1903-Koordinaten
  function entfernung(x1, y1, x2, y2: real): real;
    function rad(grad: real): real;
    begin
      rad:=grad/180*Pi();
    end;
  var
    br1, ln1, br2, ln2: real;
  begin
    br1:=rad(x1); ln1:=rad(y1);
    br2:=rad(x2); ln2:=rad(y2);
    entfernung:=6378137*ArcCos(Sin(br1)*Sin(br2)+cos(br1)*cos(br2)*cos(ln2-ln1));
  end;

var i, j: Integer;

begin
  j:=0;
  for i:=0 to InF.Count-1 do
  begin
    if CompareStr(leftstr(Trim(InF[i]),6), '<trkpt')=0 then
    begin
      // erstmal werden alle gegebenen Parameter eingelesen
      PtList[j].N:=StrtoFloat(ExtractDelimited(2, InF[i], ['"']));
      PtList[j].E:=StrtoFloat(ExtractDelimited(4, InF[i], ['"']));
      PtList[j].h:=280;  // Google exportiert leider keine Höhen -> Std-Höhe
      inc(j);
    end;
  end;
  PtList[0].d:=0;
  PtList[0].t:=0;
  for i:=1 to high(PtList) do
  begin
    // Aus den N & E-Koordinaten berechnen wir die Distanz zw. 2 Punkten
    PtList[i].d:=entfernung(PtList[i-1].N, PtList[i-1].E, PtList[i].N,PtList[i].E);
  end;
end;

// Leider ist str eine Procedure; ich brauche aber eine function
function TMainForm.real2str(r: real): String;
var
  s: String;
begin
  str(r:0:6, s);               // immer 6 Nachkommastellen ausgeben
  real2str:=s;
end;

// exportiert die Daten aus PtList im plt-Format
procedure TMainForm.expplt;
var
  i: Integer;
begin
  OutF:=tstringlist.create;
  OutF.LoadFromFile('templ.plt');
  for i:=0 to high(PtList) do
  begin
    OutF.Add(real2str(PtList[i].N)+', '+real2str(PtList[i].E)+',0, '+real2str(PtList[i].h*3.28));
  end;
  OutF[5]:=inttostr(high(PtList));      // Anz. Trackpunkte in .plt-Datei schreiben
  // Trackname in .plt-Datei schreiben
  OutF[4]:=StringReplace(OutF[4],ExtractDelimited(4,OutF[4],[',']),TrkName.Text,[rfReplaceAll]);
  OutF.SaveToFile(OutPutFile.Text);
  OutF.free;
end;

// exportiert die Daten aus PtList im Crs-Format
procedure TMainForm.expcrs;

  // Berechnet Anhand der Steigung (dh/d) die mutmassliche Geschwindigkeit und
  // daraus die benötigte Zeit

  // v=speed(1-sin(steigung)) wobei der Parameter 5 (siehe unten) bewirkt, dass
  // bei einer (base)speed von 20 km/h und
  // 20% Steigung und diese auf 3km/h sinkt, bei
  // ca 31% Steigung dann 0km/h wird. Darüber steigt sie aufgrund der Sinusfunktion
  // wieder). Nun muss ich später durch v dividieren dh. im ungünstigsten Fall
  // durch 0, was ein Fehler verursachen würde bzw. unmöglich lange Fahrzeiten mit
  // einer Geschwindigkeit nahe 0km/h (unter 3km/h ist Velofahren eh kaum möglich)
  // Als erstes limitiere ich daher die Steigung (bzw. das Gefälle) auf 20%
  // Nun muss die BaseSpeedInp noch von km/h in m/s (Faktor 3.6) umgerechnet werden.
  // Mit t=d/v erhält man schliesslich die Zeit in [s].
  function dtime(dh: Integer; d: real): real;
  var steigung: real;
  begin
    steigung:=dh/d;
    if steigung > 0 then steigung:=min(steigung, 0.2)
    else steigung:=max(steigung, -0.2);

    dtime:=d*(3.6/trueBaseSpeed)/(1-sin(steigung*5));
  end;

var
  Datum: String;
  i : Integer;
begin
  OutF:=tstringlist.create;
  OutF.LoadFromFile('templ.crs');
  OutF[5]:='      <Name>'+TrkName.Text+'</Name>';     // Schreibe Trackname
  Datum:=FormatDateTime('YYYY-MM-DD', Date)+'T';
// zuerst ein paar Vorarbeiten: Zeit mit Fkt dtime berechen und in PtList.d summieren
// 
  trueBaseSpeed:=BaseSpeed*100/(100+ReducedSpeed);
  PtList[0].t:=0;
  for i:=1 to high(PtList) do
  begin

    // Mit der Höhenveränderung & Distanz 2er Punkte errechnen wir die nötige Fahrzeit
    PtList[i].t:=PtList[i-1].t+dtime(PtList[i].h-PtList[i-1].h, PtList[i].d);
    if PtList[i].t>=RedSpeedDuration then trueBaseSpeed:=BaseSpeed;
    // nun kann d aufsummiert werden, da sie so nicht mehr gebraucht wird
    PtList[i].d:=PtList[i-1].d+PtList[i].d;
  end;
  i:=high(PtList);   // der letzte Datenpunkt wird im folgenden ein paar mal benötigt
// Time und Distance schreiben
  OutF[7]:='          <TotalTimeSeconds>'+real2str(PtList[i].t)+'</TotalTimeSeconds>';
  OutF[8]:='          <DistanceMeters>'+real2str(PtList[i].d)+'</DistanceMeters>';
// TrackBeginPosition schreiben
  OutF[10]:='            <LatitudeDegrees>'+real2str(PtList[0].N)+'</LatitudeDegrees>';
  OutF[11]:='            <LongitudeDegrees>'+real2str(PtList[0].E)+'</LongitudeDegrees>';
// TrackEndPosition schreiben
  OutF[14]:='            <LatitudeDegrees>'+real2str(PtList[i].N)+'</LatitudeDegrees>';
  OutF[15]:='            <LongitudeDegrees>'+real2str(PtList[i].E)+'</LongitudeDegrees>';
// nun können alle Punkte ausgegeben werden
  for i:=high(PtList) downto 0 do
  begin
    OutF.Insert(21, '</Trackpoint>');
    OutF.Insert(21, '  <SensorState>Absent</SensorState>');
    OutF.Insert(21, '  <DistanceMeters>'+real2str(PtList[i].d)+'</DistanceMeters>');
    OutF.Insert(21, '  <AltitudeMeters>'+Inttostr(PtList[i].h)+'</AltitudeMeters>');
    OutF.Insert(21, '  </Position>');
    OutF.Insert(21, '    <LongitudeDegrees>'+real2str(PtList[i].E)+'</LongitudeDegrees>');
    OutF.Insert(21, '    <LatitudeDegrees>'+real2str(PtList[i].N)+'</LatitudeDegrees>');
    OutF.Insert(21, '  <Position>');
    // Zeit soll immer um 0:00 Uhr beginnen, daher die 0 in Fkt IncSecond
    OutF.Insert(21, '  <Time>'+Datum+FormatDateTime('hh:nn:ss',IncSecond(0,round(PtList[i].t)))+'Z</Time>');
    OutF.Insert(21, '<Trackpoint>');
  end;
  OutF.SaveToFile(OutPutFile.Text); // File wird ohne nachzufragen überschrieben!!
  OutF.free;
end;

// exportiert die Daten aus PtList im xol-Format
procedure TMainForm.expxol;

  // rechnet wgs84 in CH1903-Koord um
  procedure wgs2ch(N, E: real; i: Integer);

  var
    phi, lambda: real;
  begin
    phi:=(N*3600-169028.66)/10000;
    lambda:=(E*3600-26782.5)/10000;
    PtList[i].N:=200147.07+308807.95*phi+3745.25*sqr(lambda)+76.63*sqr(phi)+119.79*power(phi,3)-194.56*sqr(lambda)*phi;
    PtList[i].E:=600072.37+211455.93*lambda-10938.51*lambda*phi-0.36*lambda*sqr(phi)-44.54*power(lambda,3);
  end;

var
  i: Integer;
begin
  OutF:=tstringlist.create;
  OutF.LoadFromFile('templ.xol');
  for i:=high(PtList) downto 0 do
  begin
    wgs2ch(PtList[i].N, PtList[i].E, i);
    OutF.Insert(8, '</shape>');
    OutF.Insert(8, '  </points>');
    OutF.Insert(8, '    <point x="'+InttoStr(round(PtList[i].E))+'" y="'+InttoStr(round(PtList[i].N))+'" />');
    OutF.Insert(8, '  <points>');
    OutF.Insert(8, '<shape type="waypoint">');
  end;
  OutF[3]:='    <center x="'+InttoStr(round(PtList[0].E))+'" y="'+InttoStr(round(PtList[0].N))+'" />';
  // Trackname in Zeile 7 von templ.xol anpassen
  OutF[6]:=StringReplace(OutF[6],ExtractDelimited(4,OutF[6],['"']),TrkName.Text,[rfReplaceAll]);
  OutF.SaveToFile(OutPutFile.Text);
  OutF.free;
end;

// Dispatched anhand des "Input-File Type" zur entsprechenden Importroutine und
// danach anhand des "Output-File Type" zur entsprechenden Export-Routine
procedure TMainForm.convertClick(Sender: TObject);
begin
  // überprüfe zuerst, ob überhaupt ein Trackname vorhanden ist
  if length(TrkName.Text)=0 then
  begin  // Feld Trackname ist leer: -> Focus darauf setzen und mit Meldung abbrechen
    TrkName.SetFocus;
    MessageDlg('Empty Trackname not allowed', mtError, [mbOk],1);
  end
  else  // Trackname vorhanden also Konvertierung durchführen
  begin  // Warnung falls das Output-File bereits existiert.
   if FileExists(OutPutFile.Text) then
     if MessageDlg('File exists! It WILL be overwritten', mtWarning,mbOkCancel, 1)=2 then exit;
   SetLength(PtList, StrtoInt(Nrofpoints.Text));
   case InFTyp.ItemIndex of
     0 : In_xml;
     1 : In_plt;
     2,3,4 : In_hst;   // .hst und .tcx Dateien können mit derselben Proc eingelesen werden
     5 : In_csv;
     6 : In_gpx;
   else
     messagedlg('Please select a valid Input-File Type', mtError, [mbOk], 1);
   end;
   InF.free;
   case OutFTyp.ItemIndex of
     0 : expcrs;
     1 : expplt;
     2 : expxol;
   else
     messagedlg('Please select a valid Output-File Type', mtError, [mbOk], 1);
   end;
   end;
end;

initialization
  {$i frmmain.lrs}

end.

