{******************************************************************************}
{* This file is part of SAS.Planet project.                                   *}
{*                                                                            *}
{* Copyright (C) 2007-2022, SAS.Planet development team.                      *}
{*                                                                            *}
{* SAS.Planet is free software: you can redistribute it and/or modify         *}
{* it under the terms of the GNU General Public License as published by       *}
{* the Free Software Foundation, either version 3 of the License, or          *}
{* (at your option) any later version.                                        *}
{*                                                                            *}
{* SAS.Planet 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 SAS.Planet. If not, see <http://www.gnu.org/licenses/>.         *}
{*                                                                            *}
{* https://github.com/sasgis/sas.planet.src                                   *}
{******************************************************************************}

unit u_GeometryLonLat;

interface

uses
  t_GeoTypes,
  t_Hash,
  i_InterfaceListStatic,
  i_DoublePoints,
  i_EnumDoublePoint,
  i_LonLatRect,
  i_GeometryLonLat,
  u_BaseInterfacedObject;

type
  TGeometryLonLatPoint = class(TBaseInterfacedObject, IGeometryLonLat, IGeometryLonLatPoint)
  private
    FBounds: ILonLatRect;
    FHash: THashValue;
  private
    function GetBounds: ILonLatRect;
    function GetHash: THashValue;
    function IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
    function IsSame(const APoint: IGeometryLonLatPoint): Boolean;
    function GetGoToPoint: TDoublePoint;
    function GetPoint: TDoublePoint;
  public
    constructor Create(
      const AHash: THashValue;
      const ABounds: ILonLatRect
    );
  end;

  TGeometryLonLatBase = class(TBaseInterfacedObject)
  private
    FCount: Integer;
    FBounds: ILonLatRect;
    FHash: THashValue;
    FPoints: IDoublePoints;
  private
    function GetBounds: ILonLatRect;
    function GetHash: THashValue;
    function GetCount: Integer;
    function GetPoints: PDoublePointArray;
    function GetMeta: PDoublePointsMeta;
  public
    constructor Create(
      AClosed: Boolean;
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const APoints: IDoublePoints
    ); overload;
  end;

  TGeometryLonLatMultiPoint = class(TGeometryLonLatBase, IGeometryLonLat, IGeometryLonLatMultiPoint)
  private
    function GetEnum: IEnumLonLatPoint;
    function IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
    function IsSame(const APoint: IGeometryLonLatMultiPoint): Boolean;
    function GetGoToPoint: TDoublePoint;
  public
    constructor Create(
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const APoints: IDoublePoints
    );
  end;

  TGeometryLonLatSingleLine = class(TGeometryLonLatBase, IGeometryLonLat, IGeometryLonLatLine, IGeometryLonLatSingleLine)
  private
    function GetEnum: IEnumLonLatPoint;
    function IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
    function IsSame(const ALine: IGeometryLonLatSingleLine): Boolean;
    function GetGoToPoint: TDoublePoint;
  public
    constructor Create(
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const APoints: IDoublePoints
    );
  end;

  TGeometryLonLatContour = class(TGeometryLonLatBase, IGeometryLonLat, IGeometryLonLatPolygon, IGeometryLonLatContour)
  private
    function GetEnum: IEnumLonLatPoint;
    function IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
    function IsSame(const ALine: IGeometryLonLatContour): Boolean;
    function GetGoToPoint: TDoublePoint;
  public
    constructor Create(
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const APoints: IDoublePoints
    );
  end;

  TGeometryLonLatSinglePolygon = class(TGeometryLonLatContour, IGeometryLonLatSinglePolygon)
  private
    function IsSame(const ALine: IGeometryLonLatSinglePolygon): Boolean;
    function GetOuterBorder: IGeometryLonLatContour;
    function GetHoleCount: Integer;
    function GetHoleBorder(const AIndex: Integer): IGeometryLonLatContour;
  public
    constructor Create(
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const APoints: IDoublePoints
    );
  end;

  TGeometryLonLatSinglePolygonWithHoles = class(TBaseInterfacedObject, IGeometryLonLat, IGeometryLonLatPolygon, IGeometryLonLatSinglePolygon)
  private
    FBounds: ILonLatRect;
    FHash: THashValue;
    FOuterBorder: IGeometryLonLatContour;
    FHoleList: IInterfaceListStatic;
  private
    function GetBounds: ILonLatRect;
    function GetHash: THashValue;
    function GetGoToPoint: TDoublePoint;
    function IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
  private
    function IsSame(const ALine: IGeometryLonLatSinglePolygon): Boolean;
    function GetOuterBorder: IGeometryLonLatContour;
    function GetHoleCount: Integer;
    function GetHoleBorder(const AIndex: Integer): IGeometryLonLatContour;
  public
    constructor Create(
      const ABounds: ILonLatRect;
      const AHash: THashValue;
      const AOuterBorder: IGeometryLonLatContour;
      const AHoleList: IInterfaceListStatic
    );
  end;

implementation

uses
  Math,
  SysUtils,
  u_GeoFunc,
  u_DoublePointsMetaFunc,
  u_EnumDoublePointBySingleLine;

{ TGeometryLonLatBase }

constructor TGeometryLonLatBase.Create(
  AClosed: Boolean;
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const APoints: IDoublePoints
);
begin
  Assert(Assigned(APoints));
  Assert(APoints.Count > 0, 'Empty line');
  inherited Create;
  FBounds := ABounds;
  FHash := AHash;
  FPoints := APoints;
  FCount := FPoints.Count;

  if AClosed and (FCount > 1) and DoublePointsEqual(FPoints.Points[0], FPoints.Points[FCount - 1]) then begin
    Dec(FCount);
  end;
end;

function TGeometryLonLatBase.GetBounds: ILonLatRect;
begin
  Result := FBounds;
end;

function TGeometryLonLatBase.GetCount: Integer;
begin
  Result := FCount;
end;

function TGeometryLonLatBase.GetHash: THashValue;
begin
  Result := FHash;
end;

function TGeometryLonLatBase.GetPoints: PDoublePointArray;
begin
  Result := FPoints.Points;
end;

function TGeometryLonLatBase.GetMeta: PDoublePointsMeta;
begin
  Result := FPoints.Meta;
end;

{ TGeometryLonLatMultiPoint }

constructor TGeometryLonLatMultiPoint.Create(
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const APoints: IDoublePoints
);
begin
  inherited Create(False, ABounds, AHash, APoints);
end;

function TGeometryLonLatMultiPoint.GetEnum: IEnumLonLatPoint;
begin
  Result := TEnumDoublePointBySingleLonLatLine.Create(FPoints, False,
    FPoints.Points, nil, FCount);
end;

function TGeometryLonLatMultiPoint.GetGoToPoint: TDoublePoint;
begin
  if GetCount > 0 then begin
    Result := GetPoints[0];
  end else begin
    Result := CEmptyDoublePoint;
  end;
end;

function TGeometryLonLatMultiPoint.IsSame(
  const APoint: IGeometryLonLatMultiPoint
): Boolean;
begin
  if APoint = IGeometryLonLatMultiPoint(Self) then begin
    Result := True;
    Exit;
  end;

  if not Assigned(APoint) then begin
    Result := False;
    Exit;
  end;

  if FCount <> APoint.Count then begin
    Result := False;
    Exit;
  end;

  if (FHash <> 0) and (APoint.Hash <> 0) then begin
    Result := FHash = APoint.Hash;
  end else begin
    if not FBounds.IsEqual(APoint.Bounds) then begin
      Result := False;
      Exit;
    end;

    Result := CompareMem(FPoints.Points, APoint.Points, FCount * SizeOf(TDoublePoint));
  end;
end;

function TGeometryLonLatMultiPoint.IsSameGeometry(
  const AGeometry: IGeometryLonLat
): Boolean;
var
  VPoints: IGeometryLonLatMultiPoint;
begin
  if AGeometry = nil then begin
    Result := False;
    Exit;
  end;
  if AGeometry = IGeometryLonLat(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (AGeometry.Hash <> 0) and (FHash <> AGeometry.Hash) then begin
    Result := False;
    Exit;
  end;

  Result := False;
  if Supports(AGeometry, IGeometryLonLatMultiPoint, VPoints) then begin
    Result := IsSame(VPoints);
  end;
end;

{ TGeometryLonLatSingleLine }

constructor TGeometryLonLatSingleLine.Create(
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const APoints: IDoublePoints
);
begin
  inherited Create(False, ABounds, AHash, APoints);
end;

function TGeometryLonLatSingleLine.GetEnum: IEnumLonLatPoint;
begin
  Result := TEnumDoublePointBySingleLonLatLine.Create(FPoints, False,
    FPoints.Points, FPoints.Meta, FCount);
end;

function TGeometryLonLatSingleLine.GetGoToPoint: TDoublePoint;
begin
  if GetCount > 0 then begin
    Result := GetPoints[0];
  end else begin
    Result := CEmptyDoublePoint;
  end;
end;

function TGeometryLonLatSingleLine.IsSame(const ALine: IGeometryLonLatSingleLine): Boolean;
begin
  if ALine = IGeometryLonLatSingleLine(Self) then begin
    Result := True;
    Exit;
  end;

  if FCount <> ALine.Count then begin
    Result := False;
    Exit;
  end;

  if (FHash <> 0) and (ALine.Hash <> 0) then begin
    Result := FHash = ALine.Hash;
  end else begin
    if not FBounds.IsEqual(ALine.Bounds) then begin
      Result := False;
      Exit;
    end;

    Result :=
      CompareMem(FPoints.Points, ALine.Points, FCount * SizeOf(TDoublePoint)) and
      IsSameMeta(FPoints.Meta, ALine.Meta, FCount);
  end;
end;

function TGeometryLonLatSingleLine.IsSameGeometry(const AGeometry: IGeometryLonLat): Boolean;
var
  VLine: IGeometryLonLatSingleLine;
begin
  if AGeometry = nil then begin
    Result := False;
    Exit;
  end;
  if AGeometry = IGeometryLonLat(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (AGeometry.Hash <> 0) and (FHash <> AGeometry.Hash) then begin
    Result := False;
    Exit;
  end;

  Result := False;
  if Supports(AGeometry, IGeometryLonLatSingleLine, VLine) then begin
    Result := IsSame(VLine);
  end;
end;

{ TGeometryLonLatContour }

constructor TGeometryLonLatContour.Create(
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const APoints: IDoublePoints
);
begin
  inherited Create(True, ABounds, AHash, APoints);
end;

function TGeometryLonLatContour.GetEnum: IEnumLonLatPoint;
begin
  Result := TEnumDoublePointBySingleLonLatLine.Create(FPoints, True,
    FPoints.Points, nil, FCount);
end;

function TGeometryLonLatContour.GetGoToPoint: TDoublePoint;
begin
  Result := FBounds.CalcRectCenter;
end;

function TGeometryLonLatContour.IsSame(const ALine: IGeometryLonLatContour): Boolean;
begin
  if ALine = IGeometryLonLatContour(Self) then begin
    Result := True;
    Exit;
  end;

  if FCount <> ALine.Count then begin
    Result := False;
    Exit;
  end;

  if (FHash <> 0) and (ALine.Hash <> 0) then begin
    Result := FHash = ALine.Hash;
  end else begin
    if not FBounds.IsEqual(ALine.Bounds) then begin
      Result := False;
      Exit;
    end;
    Result := CompareMem(FPoints.Points, ALine.Points, FCount * SizeOf(TDoublePoint));
  end;
end;

function TGeometryLonLatContour.IsSameGeometry(
  const AGeometry: IGeometryLonLat
): Boolean;
var
  VLine: IGeometryLonLatContour;
begin
  if AGeometry = nil then begin
    Result := False;
    Exit;
  end;
  if AGeometry = IGeometryLonLat(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (AGeometry.Hash <> 0) and (FHash <> AGeometry.Hash) then begin
    Result := False;
    Exit;
  end;

  Result := False;
  if Supports(AGeometry, IGeometryLonLatContour, VLine) then begin
    Result := IsSame(VLine);
  end;
end;

{ TGeometryLonLatSinglePolygon }

constructor TGeometryLonLatSinglePolygon.Create(
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const APoints: IDoublePoints
);
begin
  inherited Create(ABounds, AHash, APoints);
end;

function TGeometryLonLatSinglePolygon.GetHoleBorder(
  const AIndex: Integer
): IGeometryLonLatContour;
begin
  Result := nil;
  Assert(False);
end;

function TGeometryLonLatSinglePolygon.GetHoleCount: Integer;
begin
  Result := 0;
end;

function TGeometryLonLatSinglePolygon.GetOuterBorder: IGeometryLonLatContour;
begin
  Result := Self;
end;

function TGeometryLonLatSinglePolygon.IsSame(
  const ALine: IGeometryLonLatSinglePolygon
): Boolean;
begin
  Result := IsSameGeometry(ALine);
end;

{ TGeometryLonLatPoint }

constructor TGeometryLonLatPoint.Create(
  const AHash: THashValue;
  const ABounds: ILonLatRect
);
begin
  Assert(Assigned(ABounds));
  Assert(DoublePointsEqual(ABounds.TopLeft, ABounds.BottomRight));
  inherited Create;
  FHash := AHash;
  FBounds := ABounds;
end;

function TGeometryLonLatPoint.GetBounds: ILonLatRect;
begin
  Result := FBounds;
end;

function TGeometryLonLatPoint.GetGoToPoint: TDoublePoint;
begin
  Result := GetPoint;
end;

function TGeometryLonLatPoint.GetHash: THashValue;
begin
  Result := FHash;
end;

function TGeometryLonLatPoint.GetPoint: TDoublePoint;
begin
  Result := FBounds.TopLeft;
end;

function TGeometryLonLatPoint.IsSame(
  const APoint: IGeometryLonLatPoint
): Boolean;
begin
  if not Assigned(APoint) then begin
    Result := False;
    Exit;
  end;
  if APoint = IGeometryLonLatPoint(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (APoint.Hash <> 0) and (FHash <> APoint.Hash) then begin
    Result := False;
    Exit;
  end;
  Result := FBounds.IsEqual(APoint.Bounds);
end;

function TGeometryLonLatPoint.IsSameGeometry(
  const AGeometry: IGeometryLonLat
): Boolean;
var
  VPoint: IGeometryLonLatPoint;
begin
  if not Assigned(AGeometry) then begin
    Result := False;
    Exit;
  end;
  if AGeometry = IGeometryLonLat(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (AGeometry.Hash <> 0) and (FHash <> AGeometry.Hash) then begin
    Result := False;
    Exit;
  end;
  Result := False;
  if Supports(AGeometry, IGeometryLonLatPoint, VPoint) then begin
    Result := FBounds.IsEqual(VPoint.Bounds);
  end;
end;

{ TGeometryLonLatSinglePolygonWithHoles }

constructor TGeometryLonLatSinglePolygonWithHoles.Create(
  const ABounds: ILonLatRect;
  const AHash: THashValue;
  const AOuterBorder: IGeometryLonLatContour;
  const AHoleList: IInterfaceListStatic
);
begin
  Assert(Assigned(ABounds));
  Assert(Assigned(AOuterBorder));
  Assert(Assigned(AHoleList));
  Assert(AHoleList.Count > 0);
  inherited Create;
  FBounds := ABounds;
  FHash := AHash;
  FOuterBorder := AOuterBorder;
  FHoleList := AHoleList;
end;

function TGeometryLonLatSinglePolygonWithHoles.GetBounds: ILonLatRect;
begin
  Result := FBounds;
end;

function TGeometryLonLatSinglePolygonWithHoles.GetGoToPoint: TDoublePoint;
begin
  Result := FOuterBorder.GetGoToPoint;
end;

function TGeometryLonLatSinglePolygonWithHoles.GetHash: THashValue;
begin
  Result := FHash;
end;

function TGeometryLonLatSinglePolygonWithHoles.GetHoleBorder(
  const AIndex: Integer
): IGeometryLonLatContour;
begin
  Result := IGeometryLonLatContour(FHoleList.Items[AIndex]);
end;

function TGeometryLonLatSinglePolygonWithHoles.GetHoleCount: Integer;
begin
  Result := FHoleList.Count;
end;

function TGeometryLonLatSinglePolygonWithHoles.GetOuterBorder: IGeometryLonLatContour;
begin
  Result := FOuterBorder;
end;

function TGeometryLonLatSinglePolygonWithHoles.IsSame(
  const ALine: IGeometryLonLatSinglePolygon
): Boolean;
var
  i: Integer;
begin
  if ALine = nil then begin
    Result := False;
    Exit;
  end;

  if ALine = IGeometryLonLatSinglePolygon(Self) then begin
    Result := True;
    Exit;
  end;

  if (FHash <> 0) and (ALine.Hash <> 0) then begin
    Result := FHash = ALine.Hash;
  end else begin
    if FHoleList.Count <> ALine.HoleCount then begin
      Result := False;
      Exit;
    end;
    if not FOuterBorder.IsSame(ALine.OuterBorder) then begin
      Result := False;
      Exit;
    end;

    for i := 0 to FHoleList.Count - 1 do begin
      if not GetHoleBorder(i).IsSame(ALine.HoleBorder[i]) then begin
        Result := False;
        Exit;
      end;
    end;
    Result := True;
  end;
end;

function TGeometryLonLatSinglePolygonWithHoles.IsSameGeometry(
  const AGeometry: IGeometryLonLat
): Boolean;
var
  VLine: IGeometryLonLatSinglePolygon;
begin
  if AGeometry = nil then begin
    Result := False;
    Exit;
  end;
  if AGeometry = IGeometryLonLat(Self) then begin
    Result := True;
    Exit;
  end;
  if (FHash <> 0) and (AGeometry.Hash <> 0) and (FHash <> AGeometry.Hash) then begin
    Result := False;
    Exit;
  end;

  Result := False;
  if Supports(AGeometry, IGeometryLonLatSinglePolygon, VLine) then begin
    Result := IsSame(VLine);
  end;
end;

end.
