
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9292 8e941d3f-bd1b-0410-a28a-d453659cc2b4
664 lines
17 KiB
ObjectPascal
664 lines
17 KiB
ObjectPascal
{ Reads/writes GPX files
|
|
(C) 2019 Werner Pamler (user wp at Lazarus forum https://forum.lazarus.freepascal.org)
|
|
|
|
License: modified LGPL with linking exception (like RTL, FCL and LCL)
|
|
|
|
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
|
|
for details about the license.
|
|
|
|
See also: https://wiki.lazarus.freepascal.org/FPC_modified_LGPL
|
|
}
|
|
|
|
unit mvGPX;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$WARN 6058 off : Call to subroutine "$1" marked as inline is not inlined}
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, laz2_DOM, laz2_XMLRead, laz2_XMLWrite, DateUtils,
|
|
mvTypes, mvGpsObj;
|
|
|
|
type
|
|
|
|
{ TGpxReader }
|
|
|
|
TGpxReader = class
|
|
private
|
|
ID: Integer;
|
|
FMinLat, FMinLon, FMaxLat, FMaxLon: Double;
|
|
protected
|
|
procedure ReadExtensions(ANode: TDOMNode; ATrack: TGpsTrack); overload;
|
|
procedure ReadExtensions(ANode: TDOMNode; APt: TGPSPoint); overload;
|
|
function ReadPoint(ANode: TDOMNode): TGpsPoint;
|
|
procedure ReadRoute(ANode: TDOMNode; AList: TGpsObjectlist);
|
|
procedure ReadTrack(ANode: TDOMNode; AList: TGpsObjectList);
|
|
procedure ReadTracks(ANode: TDOMNode; AList: TGpsObjectList);
|
|
procedure ReadTrackSegment(ANode: TDOMNode; ATrack: TGpsTrack);
|
|
procedure ReadWayPoints(ANode: TDOMNode; AList: TGpsObjectList);
|
|
public
|
|
function LoadFromFile(AFileName: String; AList: TGpsObjectList): Integer;
|
|
function LoadFromFile(AFileName: String; AList: TGpsObjectList; out ABounds: TRealArea): Integer;
|
|
function LoadFromStream(AStream: TStream; AList: TGpsObjectList): Integer;
|
|
function LoadFromStream(AStream: TStream; AList: TGpsObjectList; out ABounds: TRealArea): Integer;
|
|
end;
|
|
|
|
{ TGpxWriter }
|
|
|
|
TGpxWriter = class
|
|
public
|
|
class procedure SaveToStream(AStream: TStream; AList: TGpsObjectList);
|
|
class procedure SaveToFile(AFileName: String; AList: TGPSObjectList);
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
Math,
|
|
mvExtraData, Graphics;
|
|
|
|
var
|
|
PointSettings: TFormatSettings;
|
|
|
|
function GetTZD: TTime;
|
|
begin
|
|
Result := -(DateUtils.OneMinute * SysUtils.GetLocalTimeOffset);
|
|
end;
|
|
|
|
function Str2DTTZD_l(S: AnsiString; out Tzd: TTime; out IsLocal: Boolean): TDateTime;
|
|
const
|
|
FMT = 'yyyy"-"mm"-"dd"T"hh":"nn":"ss';
|
|
var
|
|
I, L, Sg: Integer;
|
|
Millis: Int64;
|
|
|
|
procedure RaiseErr;
|
|
begin
|
|
raise EConvertError.Create('Bad ISO8601 format.');
|
|
end;
|
|
|
|
begin
|
|
L := Length(S);
|
|
if (L > 19) and (S[20] in ['.', ',']) then // 'YYYY-MM-DDTHH:NN:SS[.,]?.*'
|
|
begin
|
|
I := 21; // beyond the millis dot
|
|
S[20] := '.';
|
|
// Unfortunately, millis are not scanned correctly with zzz format chars.
|
|
// .8 is scanned same as .08 or .008 which is *totally* wrong!
|
|
Result := ScanDateTime(FMT {+ '"."zzz'}, S);
|
|
Millis := 0;
|
|
Sg := 0;
|
|
while (I < L) and (S[I] in ['0'..'9']) do
|
|
begin
|
|
if Sg < 3 then // Only first three
|
|
begin
|
|
Millis := Millis * 10 + (Ord(S[I]) - Ord('0'));
|
|
Inc(Sg);
|
|
end;
|
|
Inc(I); // Skip millis field
|
|
end;
|
|
case Sg of
|
|
0: RaiseErr;
|
|
1: Millis := Millis * 100;
|
|
2: Millis := Millis * 10;
|
|
end;
|
|
// Adjust with milliseconds
|
|
Result := IncMilliSecond(Result, Millis);
|
|
end
|
|
else
|
|
begin
|
|
I := 20; // beyond the seconds
|
|
Result := ScanDateTime(FMT, S);
|
|
end;
|
|
|
|
// ScanDateTime does not treat trailing characters as error
|
|
if I <= L then // Length('YYYY-MM-DDTHH:NN:SS')
|
|
begin
|
|
|
|
// Check Zulu
|
|
if (I = L) and (S[I] = 'Z') then
|
|
Tzd := 0.0 // TZD is zero
|
|
|
|
// Check TZD
|
|
else if ((L - I) in [2,4,5]) and (S[I] in ['+', '-']) then
|
|
begin
|
|
if S[I] = '-' then Sg := -1 else Sg := 1;
|
|
case (L - I) of
|
|
2: Tzd := ScanDateTime('hh', Copy(S, I + 1, 2)) * Sg;
|
|
4: Tzd := ScanDateTime('hhnn', Copy(S, I + 1, 4)) * Sg;
|
|
5: Tzd := ScanDateTime('hh:nn', Copy(S, I + 1, 5)) * Sg;
|
|
end;
|
|
end
|
|
|
|
// Garbage at the end
|
|
else
|
|
RaiseErr;
|
|
|
|
IsLocal := False; // Qualified
|
|
end
|
|
else
|
|
begin
|
|
Tzd := GetTZD; // Using local TZD!!!
|
|
IsLocal := True; // Unqualified (local time)
|
|
end;
|
|
end;
|
|
|
|
function Str2DTTZD(S: AnsiString; out Tzd: TTime): TDateTime;
|
|
var
|
|
Local: Boolean;
|
|
begin
|
|
Result := Str2DTTZD_l(S, Tzd, Local);
|
|
end;
|
|
|
|
function ExtractISODateTime(AText: String): TDateTime;
|
|
var
|
|
Tzd: TTime;
|
|
begin
|
|
try
|
|
Result := Str2DTTZD(AText, Tzd);
|
|
except
|
|
Result := NO_DATE;
|
|
end;
|
|
end;
|
|
|
|
function GetAttrValue(ANode: TDOMNode; AAttrName: string) : string;
|
|
var
|
|
i: LongWord;
|
|
Found: Boolean;
|
|
begin
|
|
Result := '';
|
|
if (ANode = nil) or (ANode.Attributes = nil) then
|
|
exit;
|
|
|
|
Found := false;
|
|
i := 0;
|
|
while not Found and (i < ANode.Attributes.Length) do begin
|
|
if ANode.Attributes.Item[i].NodeName = AAttrName then begin
|
|
Found := true;
|
|
Result := ANode.Attributes.Item[i].NodeValue;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
end;
|
|
|
|
function GetNodeValue(ANode: TDOMNode): String;
|
|
var
|
|
child: TDOMNode;
|
|
begin
|
|
Result := '';
|
|
child := ANode.FirstChild;
|
|
if Assigned(child) and ((child.NodeName = '#text') or
|
|
(child.NodeName = '#cdata-section'))
|
|
then
|
|
Result := child.NodeValue;
|
|
end;
|
|
|
|
function TryStrToGpxColor(AGpxText: String; out AColor: TColor): Boolean;
|
|
begin
|
|
if AGpxText = 'default' then
|
|
begin
|
|
AColor := clDefault;
|
|
Exit(True);
|
|
end;
|
|
if AGpxText = 'none' then
|
|
begin
|
|
AColor := clNone;
|
|
Exit(True);
|
|
end;
|
|
if Length(AGpxText) <> 6 then
|
|
Exit;
|
|
Result := TryStrToInt('$' + AGpxText, AColor);
|
|
if Result then
|
|
AColor := ((AColor and $FF) shl 16) + (AColor and $FF00) + ((AColor and $FF0000) shr 16);
|
|
end;
|
|
|
|
|
|
{ TGpxReader }
|
|
|
|
{ Loads the specified gpx file and stores the tracks, points etc. in the provided
|
|
list. All items share the same mapviewer ID which is selected randomly and
|
|
return as function result. ABounds is the geo rectangle enclosing the items. }
|
|
function TGpxReader.LoadFromFile(AFileName: String; AList: TGpsObjectList;
|
|
out ABounds: TRealArea): Integer;
|
|
var
|
|
stream: TStream;
|
|
begin
|
|
stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyNone);
|
|
try
|
|
Result := LoadFromStream(stream, AList, ABounds);
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TGpxReader.LoadFromFile(AFileName: String; AList: TGpsObjectList): Integer;
|
|
var
|
|
area: TRealArea;
|
|
begin
|
|
Result := LoadFromFile(AFileName, AList, area);
|
|
end;
|
|
|
|
{ See LoadFromFile. }
|
|
function TGpxReader.LoadFromStream(AStream: TStream; AList: TGpsObjectList;
|
|
out ABounds: TRealArea): Integer;
|
|
var
|
|
doc: TXMLDocument = nil;
|
|
begin
|
|
try
|
|
ID := random(MaxInt - 1000) + 1000;
|
|
FMinLon := 9999; FMinLat := 9999;
|
|
FMaxLon := -9999; FMaxLat := -9999;
|
|
ReadXMLFile(doc, AStream);
|
|
ReadWayPoints(doc.DocumentElement.FindNode('wpt'), AList);
|
|
ReadTracks(doc.DocumentElement.FindNode('trk'), AList);
|
|
ReadRoute(doc.DocumentElement.FindNode('rte'), AList);
|
|
ABounds.TopLeft.Lon := FMinLon;
|
|
ABounds.TopLeft.Lat := FMaxLat;
|
|
ABounds.BottomRight.Lon := FMaxLon;
|
|
ABounds.BottomRight.Lat := FMinLat;
|
|
Result := ID;
|
|
finally
|
|
doc.Free;
|
|
end;
|
|
end;
|
|
|
|
class procedure TGpxWriter.SaveToStream(AStream: TStream; AList: TGpsObjectList
|
|
);
|
|
var
|
|
Doc: TXMLDocument;
|
|
RootNode: TDOMNode;
|
|
Ex, LineEx: TDOMElement;
|
|
I: Integer;
|
|
O: TGPSObj;
|
|
|
|
function CreateTextElement(ANode: TDOMNode; ATag, AValue: DOMString): TDOMNode;
|
|
begin
|
|
Result := ANode;
|
|
Result.AppendChild(Doc.CreateElement(ATag)).AppendChild(
|
|
Doc.CreateTextNode(AValue));
|
|
end;
|
|
|
|
function ColorToGPXColor(C: TColor): LongInt;
|
|
begin
|
|
Result := ColorToRGB(C);
|
|
Result := ((Result and $FF) shl 16) + (Result and $FF00) + ((Result and $FF0000) shr 16);
|
|
end;
|
|
|
|
function ColorToGPXColorStr(C: TColor): String;
|
|
begin
|
|
if (C = clDefault)
|
|
then Result := 'default'
|
|
else if (C = clNone)
|
|
then Result := 'none'
|
|
else Result := ColorToGPXColor(C).ToHexString(6);
|
|
end;
|
|
|
|
function AddEx(AElm: TDOMElement): TDOMElement;
|
|
begin
|
|
if not Assigned(Ex) then
|
|
Ex := Doc.CreateElement('extensions');
|
|
Ex.AppendChild(AElm);
|
|
Result := AElm;
|
|
end;
|
|
|
|
function CreatePoint(AName: String; APt: TGPSPoint): TDOMElement;
|
|
var
|
|
Img: Integer = -1;
|
|
begin
|
|
Ex := Nil; LineEx := Nil;
|
|
Result := Doc.CreateElement(AName);
|
|
Result.SetAttribute('lat', APt.Lat.ToString);
|
|
Result.SetAttribute('lon', APt.Lon.ToString);
|
|
if APt.Name <> '' then
|
|
CreateTextElement(Result, 'name', APt.Name);
|
|
if APt.Elevation <> NO_ELE then
|
|
CreateTextElement(Result, 'ele', APt.Elevation.ToString);
|
|
if APt.DateTime <> NO_DATE then
|
|
CreateTextElement(Result, 'time', DateToISO8601(APt.DateTime));
|
|
if (APt is TGPSPointOfInterest) then
|
|
Img := TGPSPointOfInterest(APt).ImageIndex;
|
|
if not APt.Visible or (Img <> -1) then
|
|
begin
|
|
LineEx := AddEx(Doc.CreateElement('misc'));
|
|
if not APt.Visible then
|
|
CreateTextElement(LineEx, 'visible', '0');
|
|
if Img <> -1 then
|
|
CreateTextElement(LineEx, 'image', Img.ToString);
|
|
end;
|
|
if Assigned(Ex) then
|
|
Result.AppendChild(Ex);
|
|
end;
|
|
|
|
function CreateTrack(AName: String; ATrack: TGPSTrack): TDOMElement;
|
|
var
|
|
Seg: TDOMElement;
|
|
|
|
procedure CreateSegments;
|
|
var
|
|
P: TGPSObj;
|
|
begin
|
|
for P in ATrack.AllObjs do
|
|
begin
|
|
Seg.AppendChild(CreatePoint('trkpt', TGPSPoint(P)));
|
|
if TSegmentExtraData.MarkOf(P) = smEnd then
|
|
begin
|
|
Seg := Doc.CreateElement('trkseg');
|
|
Result.AppendChild(Seg);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Ex := Nil;
|
|
Result := Doc.CreateElement(AName);
|
|
if ATrack.Name <> '' then
|
|
CreateTextElement(Result, 'name', ATrack.Name);
|
|
if not ATrack.Visible then
|
|
begin
|
|
LineEx := AddEx(Doc.CreateElement('misc'));
|
|
CreateTextElement(LineEx, 'visible', '0');
|
|
end;
|
|
if (ATrack.LineColor <> clDefault) or (ATrack.LineWidth > 0) then
|
|
begin
|
|
LineEx := AddEx(Doc.CreateElement('line'));
|
|
CreateTextElement(LineEx, 'color', ColorToGPXColorStr(ATrack.LineColor));
|
|
CreateTextElement(LineEx, 'width', ATrack.LineWidth.ToString);
|
|
end;
|
|
if (ATrack.ConnectColor <> clNone) or (ATrack.ConnectWidth > 0) then
|
|
begin
|
|
LineEx := AddEx(Doc.CreateElement('segconn'));
|
|
CreateTextElement(LineEx, 'color', ColorToGPXColorStr(ATrack.ConnectColor));
|
|
CreateTextElement(LineEx, 'width', ATrack.ConnectWidth.ToString);
|
|
end;
|
|
if Assigned(Ex) then
|
|
Result.AppendChild(Ex);
|
|
if ATrack.Points.Count > 0 then
|
|
begin
|
|
Seg := Doc.CreateElement('trkseg');
|
|
Result.AppendChild(Seg);
|
|
CreateSegments;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Doc := TXMLDocument.Create;
|
|
try
|
|
RootNode := Doc.CreateElement('gpx');
|
|
Doc.AppendChild(RootNode);
|
|
RootNode := Doc.DocumentElement;
|
|
TDOMElement(RootNode).SetAttribute('version', '1.1');
|
|
TDOMElement(RootNode).SetAttribute('creator',
|
|
'LazMapViewer https://wiki.lazarus.freepascal.org/LazMapViewer');
|
|
|
|
Ex := Nil;
|
|
for I := 0 to Pred(AList.Count) do
|
|
begin
|
|
O := AList[I];
|
|
if O is TGPSPointOfInterest then
|
|
RootNode.AppendChild(CreatePoint('wpt', TGPSPoint(O)))
|
|
else if O is TGPSPoint then
|
|
RootNode.AppendChild(CreatePoint('wpt', TGPSPoint(O)))
|
|
else if O is TGPSTrack then
|
|
RootNode.AppendChild(CreateTrack('trk', TGPSTrack(O)))
|
|
else
|
|
{TODO};
|
|
end;
|
|
WriteXMLFile(doc, AStream);
|
|
|
|
finally
|
|
Doc.Free;
|
|
end;
|
|
end;
|
|
|
|
class procedure TGpxWriter.SaveToFile(AFileName: String; AList: TGPSObjectList);
|
|
var
|
|
Stream: TStream;
|
|
begin
|
|
Stream := TFileStream.Create(AFileName, fmCreate);
|
|
try
|
|
SaveToStream(Stream, AList);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
function TGpxReader.LoadFromStream(AStream: TStream; AList: TGpsObjectList): Integer;
|
|
var
|
|
area: TRealArea;
|
|
begin
|
|
Result := LoadFromStream(AStream, AList, area);
|
|
end;
|
|
|
|
function FindChild(ANode: TDOMNode; APath: TStringArray): TDOMNode;
|
|
var
|
|
N: TDOMNode;
|
|
begin
|
|
Result := Nil;
|
|
if not Assigned(ANode) or (Length(APath) < 1) then
|
|
Exit;
|
|
N := ANode.FirstChild;
|
|
while Assigned(N) do
|
|
begin
|
|
if APath[0] = N.NodeName then
|
|
if Length(APath) > 1
|
|
then Exit(FindChild(N, Copy(APath, 1, Pred(Length(APath)))))
|
|
else Exit(N);
|
|
N := N.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TGpxReader.ReadExtensions(ANode: TDOMNode; ATrack: TGpsTrack);
|
|
var
|
|
N: TDOMNode;
|
|
color: TColor;
|
|
inv: LongInt;
|
|
w: Double = -1;
|
|
begin
|
|
if ANode = Nil then
|
|
Exit;
|
|
N := FindChild(ANode, ['line', 'color']);
|
|
if Assigned(N) and TryStrToGpxColor(GetNodeValue(N), color) then
|
|
ATrack.LineColor := color;
|
|
N := FindChild(ANode, ['line', 'width']);
|
|
if Assigned(N) and TryStrToFloat(GetNodeValue(N), w, PointSettings) and (w > 0) then
|
|
ATrack.LineWidth := w;
|
|
N := FindChild(ANode, ['segconn', 'color']);
|
|
if Assigned(N) and TryStrToGpxColor(GetNodeValue(N), color) then
|
|
ATrack.ConnectColor := color;
|
|
N := FindChild(ANode, ['segconn', 'width']);
|
|
if Assigned(N) and TryStrToFloat(GetNodeValue(N), w, PointSettings) and (w > 0) then
|
|
ATrack.ConnectWidth := w;
|
|
N := FindChild(ANode, ['visible']);
|
|
if Assigned(N) and TryStrToInt(GetNodeValue(N), inv) then
|
|
ATrack.Visible := inv <> 0;
|
|
end;
|
|
|
|
procedure TGpxReader.ReadExtensions(ANode: TDOMNode; APt: TGPSPoint);
|
|
var
|
|
N: TDOMNode;
|
|
inv: LongInt;
|
|
begin
|
|
if ANode = Nil then
|
|
Exit;
|
|
N := FindChild(ANode, ['visible']);
|
|
if Assigned(N) and TryStrToInt(GetNodeValue(N), inv) then
|
|
APt.Visible := inv <> 0;
|
|
end;
|
|
|
|
function TGpxReader.ReadPoint(ANode: TDOMNode): TGpsPoint;
|
|
var
|
|
s, slon, slat, sName: String;
|
|
lon, lat, ele: Double;
|
|
dt: TDateTime;
|
|
node: TDOMNode;
|
|
nodeName: String;
|
|
begin
|
|
Result := nil;
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
slon := GetAttrValue(ANode, 'lon');
|
|
slat := GetAttrValue(ANode, 'lat');
|
|
if (slon = '') or (slat = '') then
|
|
exit;
|
|
|
|
if not TryStrToFloat(slon, lon, PointSettings) then
|
|
exit;
|
|
if not TryStrToFloat(slat, lat, PointSettings) then
|
|
exit;
|
|
|
|
sName := '';
|
|
dt := NO_DATE;
|
|
ele := NO_ELE;
|
|
Result := TGpsPoint.Create(lon, lat, ele, dt);
|
|
node := ANode.FirstChild;
|
|
while node <> nil do begin
|
|
nodeName := node.NodeName;
|
|
case nodeName of
|
|
'ele' :
|
|
begin
|
|
s := GetNodeValue(node);
|
|
if s <> '' then
|
|
TryStrToFloat(s, ele, PointSettings);
|
|
end;
|
|
'name':
|
|
sName := GetNodeValue(node);
|
|
'time':
|
|
begin
|
|
s := GetNodeValue(node);
|
|
if s <> '' then
|
|
dt := ExtractISODateTime(s);
|
|
end;
|
|
'extensions':
|
|
ReadExtensions(node, Result);
|
|
end;
|
|
node := node.NextSibling;
|
|
end;
|
|
Result.Name := sname;
|
|
Result.Elevation := ele;
|
|
Result.DateTime := dt;
|
|
FMinLon := Min(FMinLon, lon);
|
|
FMaxLon := Max(FMaxLon, lon);
|
|
FMinLat := Min(FMinLat, lat);
|
|
FMaxLat := Max(FMaxLat, lat);
|
|
end;
|
|
|
|
procedure TGpxReader.ReadRoute(ANode: TDOMNode; AList: TGpsObjectlist);
|
|
var
|
|
trk: TGpsTrack;
|
|
nodeName: string;
|
|
pt: TGpsPoint;
|
|
trkName: String;
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
ANode := ANode.FirstChild;
|
|
if ANode = nil then
|
|
exit;
|
|
trk := TGpsTrack.Create;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
case nodeName of
|
|
'name':
|
|
trkName := GetNodeValue(ANode);
|
|
'rtept':
|
|
begin
|
|
pt := ReadPoint(ANode);
|
|
if pt <> nil then trk.Points.Add(pt);
|
|
end;
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
trk.Name := trkName;
|
|
AList.Add(trk, ID);
|
|
end;
|
|
|
|
procedure TGpxReader.ReadTrack(ANode: TDOMNode; AList: TGpsObjectList);
|
|
var
|
|
trk: TGpsTrack;
|
|
nodeName: string;
|
|
pt: TGpsPoint;
|
|
trkName: String = '';
|
|
begin
|
|
if ANode = nil then
|
|
exit;
|
|
ANode := ANode.FirstChild;
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
trk := TGpsTrack.Create;
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
case nodeName of
|
|
'name':
|
|
trkName := GetNodeValue(ANode);
|
|
'trkseg':
|
|
ReadTrackSegment(ANode.FirstChild, trk);
|
|
'trkpt':
|
|
begin
|
|
pt := ReadPoint(ANode);
|
|
if pt <> nil then trk.Points.Add(pt);
|
|
end;
|
|
'extensions':
|
|
ReadExtensions(ANode, trk);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
trk.Name := trkName;
|
|
AList.Add(trk, ID);
|
|
end;
|
|
|
|
procedure TGpxReader.ReadTracks(ANode: TDOMNode; AList: TGpsObjectList);
|
|
var
|
|
nodeName: String;
|
|
begin
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'trk' then
|
|
ReadTrack(ANode, AList);
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
procedure TGpxReader.ReadTrackSegment(ANode: TDOMNode; ATrack: TGpsTrack);
|
|
var
|
|
gpsPt: TGpsPoint = Nil;
|
|
nodeName: String;
|
|
begin
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'trkpt' then begin
|
|
gpsPt := ReadPoint(ANode);
|
|
if gpsPt <> Nil then
|
|
ATrack.Points.Add(gpsPt);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
if Assigned(gpsPt) then // Mark the last point of the segment
|
|
gpsPt.ExtraData := TSegmentExtraData.Create(smEnd);
|
|
end;
|
|
|
|
procedure TGpxReader.ReadWayPoints(ANode: TDOMNode; AList: TGpsObjectList);
|
|
var
|
|
nodeName: String;
|
|
gpsPt: TGpsPoint;
|
|
begin
|
|
while ANode <> nil do begin
|
|
nodeName := ANode.NodeName;
|
|
if nodeName = 'wpt' then begin
|
|
gpsPt := ReadPoint(ANode);
|
|
if gpsPt <> nil then
|
|
AList.Add(gpsPt, ID);
|
|
end;
|
|
ANode := ANode.NextSibling;
|
|
end;
|
|
end;
|
|
|
|
initialization
|
|
PointSettings := DefaultFormatSettings;
|
|
PointSettings.DecimalSeparator := '.';
|
|
|
|
end.
|
|
|