330 lines
7.9 KiB
ObjectPascal
330 lines
7.9 KiB
ObjectPascal
|
|
||
|
{******************************************}
|
||
|
{ }
|
||
|
{ FastReport VCL }
|
||
|
{ Open Street Map File }
|
||
|
{ }
|
||
|
{ Copyright (c) 1998-2021 }
|
||
|
{ by Fast Reports Inc. }
|
||
|
{ }
|
||
|
{******************************************}
|
||
|
|
||
|
unit frxOSMFileFormat;
|
||
|
|
||
|
{$I frx.inc}
|
||
|
|
||
|
interface
|
||
|
|
||
|
uses
|
||
|
frxXML, Classes, frxMapHelpers, frxAnaliticGeometry;
|
||
|
|
||
|
type
|
||
|
TOSMNode = class(TTaggedElement)
|
||
|
private
|
||
|
FLatitude: Double;
|
||
|
FLongitude: Double;
|
||
|
protected
|
||
|
public
|
||
|
constructor Create(Lat, Lon: Double);
|
||
|
|
||
|
property Latitude: Double read FLatitude;
|
||
|
property Longitude: Double read FLongitude;
|
||
|
end;
|
||
|
|
||
|
TOSMWay = class(TTaggedElement)
|
||
|
private
|
||
|
function GetCount: integer;
|
||
|
function GetShapeType: TShapeType;
|
||
|
function GetNodes(Index: Integer): String;
|
||
|
protected
|
||
|
FNodeRefs: TStringList;
|
||
|
public
|
||
|
constructor Create;
|
||
|
destructor Destroy; override;
|
||
|
|
||
|
procedure AddNodeRef(const stNodeRef: String);
|
||
|
|
||
|
property Count: integer read GetCount;
|
||
|
property ShapeType: TShapeType read GetShapeType;
|
||
|
property NodeRefs[Index: Integer]: String read GetNodes;
|
||
|
end;
|
||
|
|
||
|
TOSMFile = class
|
||
|
private
|
||
|
FXmin: Double;
|
||
|
FYmin: Double;
|
||
|
FXmax: Double;
|
||
|
FYmax: Double;
|
||
|
function GetCountOfNodes: Integer;
|
||
|
function GetCountOfWays: Integer;
|
||
|
function GetWays(Index: integer): TOSMWay;
|
||
|
function GetNodes(Index: integer): TOSMNode;
|
||
|
procedure LoadFrom(const FileName: string; Stream: TStream = nil);
|
||
|
protected
|
||
|
FOSMNodes: TStringList;
|
||
|
FOSMWays: TStringList;
|
||
|
FSumTags: TfrxSumStringList;
|
||
|
FValidBounds: Boolean;
|
||
|
procedure LoadBounds(XMLItem: TfrxXMLItem);
|
||
|
procedure LoadNode(XMLItem: TfrxXMLItem);
|
||
|
procedure LoadWay(XMLItem: TfrxXMLItem);
|
||
|
procedure ParseItem(XMLItem: TfrxXMLItem);
|
||
|
function ValidUTF8(st: String): String;
|
||
|
public
|
||
|
constructor Create(const FileName: string; Stream: TStream = nil); overload;
|
||
|
destructor Destroy; override;
|
||
|
function IsGetNodeAsPoint(const iWay, iNode: integer; out DP: TDoublePoint): boolean;
|
||
|
function IsValidBounds: Boolean;
|
||
|
|
||
|
property Xmin: Double read FXmin;
|
||
|
property Ymin: Double read FYmin;
|
||
|
property Xmax: Double read FXmax;
|
||
|
property Ymax: Double read FYmax;
|
||
|
|
||
|
property CountOfWays: Integer read GetCountOfWays;
|
||
|
property Ways[Index: integer]: TOSMWay read GetWays;
|
||
|
property CountOfNodes: Integer read GetCountOfNodes;
|
||
|
property Nodes[Index: integer]: TOSMNode read GetNodes;
|
||
|
|
||
|
property SumTags: TfrxSumStringList read FSumTags;
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
SysUtils, Math, frxUtils;
|
||
|
|
||
|
{ TOSMFile }
|
||
|
|
||
|
constructor TOSMFile.Create(const FileName: string; Stream: TStream = nil);
|
||
|
begin
|
||
|
FOSMNodes := TStringList.Create;
|
||
|
FOSMWays := TStringList.Create;
|
||
|
FSumTags := TfrxSumStringList.Create;
|
||
|
FValidBounds := False;
|
||
|
|
||
|
LoadFrom(FileName, Stream);
|
||
|
|
||
|
FOSMNodes.Sorted := True;
|
||
|
end;
|
||
|
|
||
|
destructor TOSMFile.Destroy;
|
||
|
procedure FreeWithObjects(var StringList: TStringList);
|
||
|
var
|
||
|
i: Integer;
|
||
|
begin
|
||
|
for i := 0 to StringList.Count - 1 do
|
||
|
StringList.Objects[i].Free;
|
||
|
StringList.Free;
|
||
|
end;
|
||
|
begin
|
||
|
FreeWithObjects(FOSMNodes);
|
||
|
FreeWithObjects(FOSMWays);
|
||
|
|
||
|
FreeAndNil(FSumTags);
|
||
|
|
||
|
inherited;
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.GetCountOfNodes: Integer;
|
||
|
begin
|
||
|
Result := FOSMNodes.Count;
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.GetCountOfWays: Integer;
|
||
|
begin
|
||
|
Result := FOSMWays.Count;
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.GetNodes(Index: integer): TOSMNode;
|
||
|
begin
|
||
|
Result := TOSMNode(FOSMNodes.Objects[Index]);
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.GetWays(Index: integer): TOSMWay;
|
||
|
begin
|
||
|
Result := TOSMWay(FOSMWays.Objects[Index]);
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.IsGetNodeAsPoint(const iWay, iNode: integer; out DP: TDoublePoint): boolean;
|
||
|
var
|
||
|
NodeIndex: Integer;
|
||
|
Node: TOSMNode;
|
||
|
begin
|
||
|
Result := FOSMNodes.Find(Ways[iWay].NodeRefs[iNode], NodeIndex);
|
||
|
if Result then
|
||
|
begin
|
||
|
Node := Nodes[NodeIndex];
|
||
|
DP := DoublePoint(Node.Longitude, Node.Latitude);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.IsValidBounds: Boolean;
|
||
|
begin
|
||
|
Result := FValidBounds;
|
||
|
end;
|
||
|
|
||
|
procedure TOSMFile.LoadBounds(XMLItem: TfrxXMLItem);
|
||
|
begin
|
||
|
if FValidBounds then
|
||
|
begin
|
||
|
FXmin := Min(FXmin, frxStrToFloat(XMLItem.Prop['minlon']));
|
||
|
FYmin := Min(FYmin, frxStrToFloat(XMLItem.Prop['minlat']));
|
||
|
FXmax := Max(FXmax, frxStrToFloat(XMLItem.Prop['maxlon']));
|
||
|
FYmax := Max(FYmax, frxStrToFloat(XMLItem.Prop['maxlat']));
|
||
|
end
|
||
|
else
|
||
|
begin
|
||
|
FXmin := frxStrToFloat(XMLItem.Prop['minlon']);
|
||
|
FYmin := frxStrToFloat(XMLItem.Prop['minlat']);
|
||
|
FXmax := frxStrToFloat(XMLItem.Prop['maxlon']);
|
||
|
FYmax := frxStrToFloat(XMLItem.Prop['maxlat']);
|
||
|
FValidBounds := True;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TOSMFile.LoadFrom(const FileName: string; Stream: TStream);
|
||
|
var
|
||
|
MapXMLDocument: TfrxMapXMLDocument;
|
||
|
Item: TfrxXMLItem;
|
||
|
begin
|
||
|
Item := nil;
|
||
|
MapXMLDocument := TfrxMapXMLDocument.Create;
|
||
|
try
|
||
|
// TODO: refactor this ugly code. It's just fast adaptation of old code to new functionality
|
||
|
if Assigned(Stream) then
|
||
|
MapXMLDocument.InitMapXMLStream(Stream)
|
||
|
else
|
||
|
MapXMLDocument.InitMapXMLFile(FileName);
|
||
|
repeat
|
||
|
Item := TfrxXMLItem.Create;
|
||
|
if MapXMLDocument.IsReadItem(Item) then
|
||
|
ParseItem(Item)
|
||
|
else
|
||
|
Break;
|
||
|
Item.Free;
|
||
|
until False;
|
||
|
finally
|
||
|
Item.Free;
|
||
|
MapXMLDocument.DoneMapXMLFile;
|
||
|
MapXMLDocument.Free;
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
procedure TOSMFile.LoadNode(XMLItem: TfrxXMLItem);
|
||
|
var
|
||
|
id: String;
|
||
|
lat, lon: Double;
|
||
|
OSMNode: TOSMNode;
|
||
|
loName: String;
|
||
|
i: Integer;
|
||
|
begin
|
||
|
id := XMLItem.Prop['id'];
|
||
|
lat := frxStrToFloat(XMLItem.Prop['lat']);
|
||
|
lon := frxStrToFloat(XMLItem.Prop['lon']);
|
||
|
OSMNode := TOSMNode.Create(lat, lon);
|
||
|
for i := 0 to XMLItem.Count - 1 do
|
||
|
with XMLItem.Items[i] do
|
||
|
begin
|
||
|
loName := AnsiLowerCase(Name);
|
||
|
if loName = 'tag' then
|
||
|
begin
|
||
|
OSMNode.AddTag(ValidUTF8(Prop['k']), ValidUTF8(Prop['v']));
|
||
|
SumTags.AddSum(ValidUTF8(Prop['k']));
|
||
|
end;
|
||
|
end;
|
||
|
FOSMNodes.AddObject(id, OSMNode);
|
||
|
end;
|
||
|
|
||
|
procedure TOSMFile.LoadWay(XMLItem: TfrxXMLItem);
|
||
|
var
|
||
|
id: String;
|
||
|
OSMWay: TOSMWay;
|
||
|
loName: String;
|
||
|
i: Integer;
|
||
|
begin
|
||
|
id := XMLItem.Prop['id'];
|
||
|
OSMWay := TOSMWay.Create;
|
||
|
for i := 0 to XMLItem.Count - 1 do
|
||
|
with XMLItem.Items[i] do
|
||
|
begin
|
||
|
loName := AnsiLowerCase(Name);
|
||
|
if loName = 'nd' then
|
||
|
OSMWay.AddNodeRef(Prop['ref'])
|
||
|
else if loName = 'tag' then
|
||
|
begin
|
||
|
OSMWay.AddTag(ValidUTF8(Prop['k']), ValidUTF8(Prop['v']));
|
||
|
SumTags.AddSum(ValidUTF8(Prop['k']));
|
||
|
end;
|
||
|
end;
|
||
|
FOSMWays.AddObject(id, OSMWay);
|
||
|
end;
|
||
|
|
||
|
procedure TOSMFile.ParseItem(XMLItem: TfrxXMLItem);
|
||
|
var
|
||
|
loName: String;
|
||
|
begin
|
||
|
loName := AnsiLowerCase(XMLItem.Name);
|
||
|
if loName = 'bounds' then LoadBounds(XMLItem)
|
||
|
else if loName = 'node' then LoadNode(XMLItem)
|
||
|
else if loName = 'way' then LoadWay(XMLItem)
|
||
|
else if loName = 'relation' then { TODO : Skipped for now};
|
||
|
end;
|
||
|
|
||
|
function TOSMFile.ValidUTF8(st: String): String;
|
||
|
begin
|
||
|
Result := {$IFDEF DELPHI12} st;
|
||
|
{$ELSE} UTF8Decode(AnsiString(st));
|
||
|
{$ENDIF}
|
||
|
end;
|
||
|
|
||
|
{ TOSMNode }
|
||
|
|
||
|
constructor TOSMNode.Create(Lat, Lon: Double);
|
||
|
begin
|
||
|
inherited Create;
|
||
|
|
||
|
FLatitude := Lat;
|
||
|
FLongitude := Lon;
|
||
|
end;
|
||
|
|
||
|
{ TOSMWay }
|
||
|
|
||
|
procedure TOSMWay.AddNodeRef(const stNodeRef: String);
|
||
|
begin
|
||
|
FNodeRefs.Add(stNodeRef);
|
||
|
end;
|
||
|
|
||
|
constructor TOSMWay.Create;
|
||
|
begin
|
||
|
inherited Create;
|
||
|
|
||
|
FNodeRefs := TStringList.Create;
|
||
|
end;
|
||
|
|
||
|
destructor TOSMWay.Destroy;
|
||
|
begin
|
||
|
FNodeRefs.Free;
|
||
|
|
||
|
inherited;
|
||
|
end;
|
||
|
|
||
|
function TOSMWay.GetCount: integer;
|
||
|
begin
|
||
|
Result := FNodeRefs.Count;
|
||
|
end;
|
||
|
|
||
|
function TOSMWay.GetNodes(Index: Integer): String;
|
||
|
begin
|
||
|
Result := FNodeRefs[Index];
|
||
|
end;
|
||
|
|
||
|
function TOSMWay.GetShapeType: TShapeType;
|
||
|
begin
|
||
|
if Count = 1 then Result := stPoint
|
||
|
else if FNodeRefs[0] = FNodeRefs[Count - 1] then Result := stPolygon
|
||
|
else Result := stPolyLine;
|
||
|
end;
|
||
|
|
||
|
end.
|