FastReport_2022_VCL/Source/frxMapHelpers.pas
2024-01-01 16:13:08 +01:00

1520 lines
39 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport VCL }
{ Map Helpers }
{ }
{ Copyright (c) 1998-2021 }
{ by Fast Reports Inc. }
{ }
{******************************************}
unit frxMapHelpers;
interface
{$I frx.inc}
uses
frxClass, Classes, Contnrs, frxXML, Controls, Types,
{$IFNDEF FPC}
Windows,
{$ELSE}
LCLType, LCLIntf, LCLProc,
{$ENDIF}
Graphics, Menus, frxAnaliticGeometry;
type
TLayerType = (ltMapFile, ltApplication, ltInteractive, ltGeodata);
TShapeType = (stNone, stPoint, stPolyLine, stPolygon,
stRect, stDiamond, stEllipse, stPicture, stLegend, stTemplate,
stMultiPoint, stMultiPolyLine, stMultiPolygon);
TShapeData = class
private
FData: TDoublePointMatrix;
FShapeType: TShapeType;
FWidestPartBounds: TfrxRect;
FShapeCenter: TfrxPoint;
FPicture: TPicture;
FConstrainProportions: Boolean;
FFont: TFont;
FLegendText: TStringList;
FTemplateName: String;
function GetLegend(const Name: String): String;
function GetPoint: TDoublePoint;
procedure SetPoint(const Value: TDoublePoint);
function GetMultiLine(iPart, iPoint: Integer): TDoublePoint;
procedure SetMultiLine(iPart, iPoint: Integer; const Value: TDoublePoint);
function GetPartCount: Integer;
procedure SetPartCount(const Value: Integer);
function GetMultiLineCount(iPart: Integer): Integer;
procedure SetMultiLineCount(iPart: Integer; const Value: Integer);
function GetRect: TDoubleRect;
procedure SetRect(const Value: TDoubleRect);
function GetMultiPoint(iPoint: Integer): TDoublePoint;
function GetMultiPointCount: Integer;
procedure SetMultiPoint(iPoint: Integer; const Value: TDoublePoint);
procedure SetMultiPointCount(const Value: Integer);
protected
FTags: TStringList; // SparialData
public
constructor CreateFull(iParts: Integer; AShapeType: TShapeType; ATags: TStrings);
constructor CreatePoint(ATags: TStrings; X, Y: Double);
constructor CreateRect(ATags: TStrings; AShapeType: TShapeType; DR: TDoubleRect);
constructor CreatePoly(AShapeType: TShapeType; ATags: TStrings; iPoints: Integer);
constructor CreateEmpty(AShapeType: TShapeType; ATags: TStrings);
destructor Destroy; override;
procedure AddPart(iPoints: Integer);
function IsGetColumnList(List: TStrings): Boolean;
procedure CalcBounds;
procedure GetPolyPoints(var Points: TDoublePointArray; iPart: Integer);
function IsClosed: Boolean;
function IsMercatorSuitable: Boolean;
procedure ReadStringList(var SL: TStringList; Reader: TReader);
procedure ReadTags(Reader: TReader);
procedure ReadData(Reader: TReader);
procedure ReadPicture(Reader: TReader);
procedure ReadFont(Reader: TReader);
procedure WriteStringList(SL: TStringList; Writer: TWriter);
procedure WriteTags(Writer: TWriter);
procedure WriteData(Writer: TWriter);
procedure WritePicture(Writer: TWriter);
procedure WriteFont(Writer: TWriter);
property Point: TDoublePoint read GetPoint write SetPoint;
property Rect: TDoubleRect read GetRect write SetRect;
property MultiPoint[iPoint: Integer]: TDoublePoint read GetMultiPoint write SetMultiPoint;
property MultiPointCount: Integer read GetMultiPointCount write SetMultiPointCount;
property MultiLine[iPart, iPoint: Integer]: TDoublePoint read GetMultiLine write SetMultiLine;
property PartCount: Integer read GetPartCount write SetPartCount;
property MultiLineCount[iPart: Integer]: Integer read GetMultiLineCount write SetMultiLineCount;
property Legend[const Name: String]: String read GetLegend;
property ShapeType: TShapeType read FShapeType;
property WidestPartBounds: TfrxRect read FWidestPartBounds;
property ShapeCenter: TfrxPoint read FShapeCenter;
property Tags: TStringList read FTags; // SparialData
property Picture: TPicture read FPicture;
property ConstrainProportions: Boolean read FConstrainProportions write FConstrainProportions;
property Font: TFont read FFont;
property LegendText: TStringList read FLegendText;
property TemplateName: String read FTemplateName write FTemplateName;
end;
TMapToCanvasCoordinateConverter = class
private
FMercatorProjection: Boolean;
FIsHasData: Boolean;
procedure SetMercatorProjection(const Value: Boolean);
protected
FXmin, FYmin, FXmax, FYmax: Extended; // Map
FXRange, FYRange, FYmaxTransformed: Extended; // speedup
FWidth, FHeight, FOffsetX, FOffsetY: Extended; // Canvas
FShapeActive: Boolean;
FShapeZoom: Extended;
FShapeOffset, FShapeCenter: TfrxPoint;
FUseOffset: Boolean;
function YTransform(Y: Extended): Extended;
function ConvertMercator(Y: Extended): Extended;
function MapTransform(P: TfrxPoint): TfrxPoint; overload;
function MapTransform(X, Y: Extended): TfrxPoint; overload;
function ShapeTransform(P: TfrxPoint): TfrxPoint;
public
procedure Init;
procedure IncludeRect(LayerRect: TfrxRect);
function AspectRatio: Extended;
procedure SetCanvasSize(CanvasSize: TfrxPoint);
procedure SetOffset(OffsetX, OffsetY: Extended);
procedure ReadDFM(Stream: TStream);
procedure WriteDFM(Stream: TStream);
procedure Read(Reader: TReader);
procedure Write(Writer: TWriter);
procedure IgnoreShape;
procedure InitShape(AOffsetX, AOffsetY, AZoom: Extended; AShapeCenter: TfrxPoint);
function Transform(X, Y: Extended): TfrxPoint; overload;
function Transform(DoublePoint: TDoublePoint): TfrxPoint; overload;
function Transform(frxPoint: TfrxPoint): TfrxPoint; overload;
function TransformOffset(frxPoint: TfrxPoint): TfrxPoint; overload;
function TransformRect(R: TfrxRect): TfrxRect; // Flip Top <--> Bottom
function CanvasToMap(Canvas: TfrxPoint): TfrxPoint;
property UseOffset: Boolean read FUseOffset write FUseOffset;
property MercatorProjection: Boolean read FMercatorProjection write SetMercatorProjection;
property IsHasData: Boolean read FIsHasData;
end;
TTaggedElement = class
protected
FTags: TStringList;
public
constructor Create;
destructor Destroy; override;
procedure AddTag(const stName, stValue: String);
function IsHaveAllTags(LayerTags: TStrings): Boolean;
function IsHaveAnyTag(LayerTags: TStrings): Boolean;
property Tags: TStringList read FTags;
end;
TValueDlm = (vdUnknown, vdApostrophe, vdQuote);
TfrxMapXMLReader = class(TfrxXMLReader)
protected
FValueDlm: TValueDlm;
function IsLastSlash(const InSt: String): Boolean;
function IsFirstSlash(const InSt: String): Boolean;
procedure ReadValuedItem(var {$IFDEF Delphi12}NameS, ValueS{$ELSE}Name, Value{$ENDIF}, Text: String);
procedure DefineValueDlm(Text: String);
procedure ChangeValueDlm(var Text: String);
public
constructor Create(Stream: TStream);
function IsReadMapXMLRootItem(Item: TfrxXMLItem): Boolean;
function IsReadMapXMLItem(Item: TfrxXMLItem): Boolean;
end;
TfrxMapXMLDocument = class(TfrxXMLDocument)
protected
FMapXMLStream: TStream;
FMapXMLStreamReader: TfrxMapXMLReader;
procedure InitMapXMLInternal(Stream: TStream);
public
procedure InitMapXMLFile(const FileName: String);
procedure InitMapXMLStream(Stream: TStream);
procedure DoneMapXMLFile;
function IsReadItem(Item: TfrxXMLItem): Boolean;
end;
TfrxSumStringList = class (TStringList)
private
function GetSum(i: Integer): Integer;
procedure SetSum(i: Integer; const Value: Integer);
public
procedure AddSum(st: String);
procedure SortSum;
property Sum[i: Integer]: Integer read GetSum write SetSum;
end;
type
TfrxClippingRect = class
private
FR: TfrxRect;
FActive: Boolean;
public
constructor Create;
procedure Init(Active: Boolean; R: TfrxRect);
function IsCircleInside(Circle: TCircle): Boolean;
function IsPolygonCover(PolyPoints: TPointArray): Boolean; overload;
function IsPolygonCover(PolyPoints: TDoublePointArray): Boolean; overload;
function IsPolygonCover(ShapeData: TShapeData; iPart: Integer): Boolean; overload;
function IsPointInside(P: TfrxPoint): Boolean;
function IsPolyLineInside(PolyPoints: TPointArray): Boolean; overload;
function IsPolyLineInside(PolyPoints: TDoublePointArray): Boolean; overload;
function IsPolyLineInside(ShapeData: TShapeData; iPart: Integer): Boolean; overload;
function IsPolygonInside(PolyPoints: TPointArray): Boolean; overload;
function IsPolygonInside(PolyPoints: TDoublePointArray): Boolean; overload;
function IsPolygonInside(ShapeData: TShapeData; iPart: Integer): Boolean; overload;
function IsSegmentInside(S: TSegment): Boolean;
function IsRectInside(Rect: TRect): Boolean;
function IsDiamondInside(Rect: TRect): Boolean;
end;
procedure Simplify(Points: TDoublePointArray; Accuracy: Extended; var UsedCount: Integer);
function SafeFileName(FileName: String): String;
procedure Log(Text: String); overload;
procedure Log(Strings: TStrings); overload;
procedure Log(Stream: TStream); overload;
function ToHex(b: Byte): string;
procedure LogOn;
procedure LogOff;
procedure Translate(WinControl: TWinControl);
procedure TranslateMenu(Menu: TMenu);
function PlatformFileName(const FileName: string): string;
function IsMercatorLatitude(YMin, YMax: Extended): Boolean;
implementation
uses
Math, SysUtils, Dialogs, frxUtils, frxPictureGraphics;
type
TPictureFormat = (pfUnknown, pfPicture);
{ Functions}
var
UseLog: Boolean;
const
MaxLatitude = 90;
MaxMercatorLatitude = 85.0511287798066;
function IsMercatorLatitude(YMin, YMax: Extended): Boolean;
begin
Result := (YMax <= MaxLatitude) and (YMax > -MaxMercatorLatitude) and
(YMin >= -MaxLatitude) and (YMin < MaxMercatorLatitude);
end;
function PlatformFileName(const FileName: string): string;
begin
Result := {$IFDEF NONWINFPC} ExpandUNCFileName(FileName)
{$ELSE} FileName
{$ENDIF};
end;
procedure SaveFontToStream(Stream: TStream; AFont: TFont);
var
LogFont: TLogFont;
Color: TColor;
begin
if GetObject(AFont.Handle, SizeOf(LogFont), @LogFont) = 0 then
RaiseLastOSError;
Stream.WriteBuffer(LogFont, SizeOf(LogFont));
Color := AFont.Color;
Stream.WriteBuffer(Color, SizeOf(Color));
end;
procedure LoadFontFromStream(Stream: TStream; AFont: TFont);
var
LogFont: TLogFont;
F: HFONT;
Color: TColor;
begin
Stream.ReadBuffer(LogFont, SizeOf(LogFont));
F := CreateFontIndirect(LogFont);
if F = 0 then
RaiseLastOSError;
AFont.Handle := F;
Stream.ReadBuffer(Color, SizeOf(Color));
AFont.Color := Color;
end;
function ToHex(b: Byte): string;
const
d: string = '0123456789abcdef';
begin
Result := d[1 + b div 16] + d[1 + b mod 16];
end;
procedure LogOn;
begin
UseLog := True;
end;
procedure LogOff;
begin
UseLog := False;
end;
procedure Log(Stream: TStream);
var
SL: TStringList;
OldPosition: Int64;
begin
if not UseLog then
Exit;
SL := TStringList.Create;
try
OldPosition := Stream.Position;
Stream.Position := 0;
SL.LoadFromStream(Stream);
Stream.Position := OldPosition;
Log(SL);
finally
SL.Free;
end;
end;
procedure Log(Strings: TStrings);
begin
if not UseLog then
Exit;
Log(Strings.Text);
end;
procedure Log(Text: String);
var
F: TextFile;
FileName: String;
begin
if not UseLog then
Exit;
FileName := ExtractFilePath(Paramstr(0)) + 'Log.txt';
AssignFile(F, FileName);
if FileExists(FileName) then
Append(F)
else
Rewrite(F);
WriteLn(F, text);
CloseFile(F);
end;
function SafeFileName(FileName: String): String;
begin
Result := AnsiLowerCase(ExpandUNCFileName(FileName));
end;
procedure Simplify(Points: TDoublePointArray; Accuracy: Extended; var UsedCount: Integer);
function Dist(i1, i2: integer): Extended;
begin
Result := Sqrt(Sqr(Points[i1].X - Points[i2].X) + Sqr(Points[i1].Y - Points[i2].Y));
end;
var
iPoint, AccuracyCount: Integer;
begin
AccuracyCount := 1;
for iPoint := 1 to UsedCount - 1 do
if Dist(AccuracyCount - 1, iPoint) > Accuracy then
begin
Points[AccuracyCount] := Points[iPoint];
Inc(AccuracyCount);
end;
UsedCount := AccuracyCount;
end;
procedure TranslateMenu(Menu: TMenu);
var
i: Integer;
begin
for i := 0 to Menu.Items.Count - 1 do
with Menu.Items[i] do
if Tag > 0 then
Caption := GetStr(IntToStr(Tag));
end;
procedure Translate(WinControl: TWinControl);
procedure AssignTexts(Root: TControl);
var
i: Integer;
begin
with Root do
begin
if Tag > 0 then
SetTextBuf(PChar(GetStr(IntToStr(Tag))));
if Root is TWinControl then
with Root as TWinControl do
for i := 0 to ControlCount - 1 do
if Controls[i] is TControl then
AssignTexts(Controls[i] as TControl);
end;
end;
begin
AssignTexts(WinControl);
if WinControl.UseRightToLeftAlignment then
WinControl.FlipChildren(True);
end;
{ TShapeData }
procedure TShapeData.AddPart(iPoints: Integer);
begin
PartCount := PartCount + 1;
MultiLineCount[PartCount - 1] := iPoints;
end;
procedure TShapeData.CalcBounds;
var
iPart, iPoint: Integer;
ShapeBounds, PartBounds: TfrxRect;
begin
case ShapeType of
stPolyLine, stPolygon, stTemplate,
stMultiPoint, stMultiPolyLine, stMultiPolygon:
begin
InitRect(ShapeBounds, MultiLine[0, 0]);
FWidestPartBounds := ShapeBounds;
for iPart := 0 to PartCount - 1 do
begin
InitRect(PartBounds, MultiLine[iPart, 0]);
for iPoint := 0 to MultiLineCount[iPart] - 1 do
ExpandRect(PartBounds, MultiLine[iPart, iPoint]);
ExpandRect(ShapeBounds, PartBounds);
if RectWidth(FWidestPartBounds) < RectWidth(PartBounds) then
FWidestPartBounds := PartBounds;
end;
FShapeCenter := CenterRect(ShapeBounds);
end;
stRect, stDiamond, stEllipse, stPicture, stLegend:
begin
FWidestPartBounds := frxCanonicalRect(Rect);
FShapeCenter := CenterRect(FWidestPartBounds);
end;
stPoint:
with Point do
FShapeCenter := frxPoint(X, Y);
else
raise Exception.Create('Unknown ShapeType');
end
end;
constructor TShapeData.CreateEmpty(AShapeType: TShapeType; ATags: TStrings);
begin
CreateFull(0, AShapeType, ATags);
end;
constructor TShapeData.CreateFull(iParts: Integer; AShapeType: TShapeType; ATags: TStrings);
begin
PartCount := iParts;
FShapeType := AShapeType;
FTags := TStringList.Create;
FTags.Assign(ATags);
case ShapeType of
stPicture:
FPicture := TPicture.Create;
stLegend:
begin
FFont := TFont.Create;
FLegendText := TStringList.Create;
end;
stTemplate:
FTemplateName := '';
end;
end;
constructor TShapeData.CreatePoly(AShapeType: TShapeType; ATags: TStrings; iPoints: Integer);
begin
CreateFull(1, AShapeType, ATags);
MultiLineCount[0] := iPoints;
end;
constructor TShapeData.CreateRect(ATags: TStrings; AShapeType: TShapeType; DR: TDoubleRect);
begin
CreateFull(1, AShapeType, ATags);
MultiLineCount[0] := 2;
Rect := DR;
end;
constructor TShapeData.CreatePoint(ATags: TStrings; X, Y: Double);
begin
CreateFull(1, stPoint, ATags);
MultiLineCount[0] := 1;
Point := DoublePoint(X, Y);
end;
destructor TShapeData.Destroy;
var
iParts: Integer;
begin
for iParts := 0 to High(FData) do
Finalize(FData[iParts]);
Finalize(FData);
FTags.Free;
FPicture.Free;
FFont.Free;
FLegendText.Free;
inherited;
end;
function TShapeData.GetLegend(const Name: String): String;
begin
Result := FTags.Values[Name];
end;
function TShapeData.GetMultiLine(iPart, iPoint: Integer): TDoublePoint;
begin
Result := FData[iPart, iPoint];
end;
function TShapeData.GetMultiLineCount(iPart: Integer): Integer;
begin
Result := Length(FData[iPart]);
end;
function TShapeData.GetMultiPoint(iPoint: Integer): TDoublePoint;
begin
Result := FData[0, iPoint];
end;
function TShapeData.GetMultiPointCount: Integer;
begin
Result := Length(FData[0]);
end;
function TShapeData.GetPartCount: Integer;
begin
Result := Length(FData);
end;
function TShapeData.GetPoint: TDoublePoint;
begin
Result := GetMultiLine(0, 0);
end;
procedure TShapeData.GetPolyPoints(var Points: TDoublePointArray; iPart: Integer);
var
iPoint: Integer;
begin
SetLength(Points, MultiLineCount[iPart]);
for iPoint := 0 to High(Points) do
Points[iPoint] := MultiLine[iPart, iPoint];
end;
function TShapeData.GetRect: TDoubleRect;
begin
Result.TopLeft := GetMultiLine(0, 0);
Result.BottomRight := GetMultiLine(0, 1);
end;
function TShapeData.IsClosed: Boolean; // The first point coincides with the last
var
iPart: Integer;
DP: TDoublePoint;
begin
Result := False;
for iPart := 0 to High(FData) do
if High(FData[iPart]) > 1 then
begin
DP := FData[iPart, 0];
with FData[iPart, High(FData[iPart])] do
Result := (DP.X = X) and (DP.Y = Y);
if Result then
Break;
end;
end;
function TShapeData.IsGetColumnList(List: TStrings): Boolean;
var
iTag: Integer;
begin
Result := FTags.Count > 0;
for iTag := 0 to FTags.Count - 1 do
List.Add(FTags.Names[iTag]);
end;
function TShapeData.IsMercatorSuitable: Boolean;
begin
with FWidestPartBounds do
Result := IsMercatorLatitude(Top, Bottom);
end;
procedure TShapeData.ReadData(Reader: TReader);
var
iPart, iPoint: Integer;
x1, x2: Extended;
begin
FWidestPartBounds.Left := Reader.ReadFloat;
FWidestPartBounds.Top := Reader.ReadFloat;
FWidestPartBounds.Right := Reader.ReadFloat;
FWidestPartBounds.Bottom := Reader.ReadFloat;
FShapeCenter.X := Reader.ReadFloat;
FShapeCenter.Y := Reader.ReadFloat;
FShapeType := TShapeType(Reader.ReadInteger);
PartCount := Reader.ReadInteger;
for iPart := 0 to PartCount - 1 do
begin
MultiLineCount[iPart] := Reader.ReadInteger;
for iPoint := 0 to MultiLineCount[iPart] - 1 do
begin
x1 := Reader.ReadFloat;
x2 := Reader.ReadFloat;
MultiLine[iPart, iPoint] := DoublePoint(x1, x2);
end;
end;
case ShapeType of
stPicture:
begin
FConstrainProportions := Reader.ReadBoolean;
ReadPicture(Reader);
end;
stLegend:
begin
ReadFont(Reader);
ReadStringList(FLegendText, Reader);
end;
stTemplate:
FTemplateName := Reader.ReadString;
end;
end;
procedure TShapeData.ReadFont(Reader: TReader);
var
MemoryStream: TMemoryStream;
begin
FFont.Free;
FFont := TFont.Create;
MemoryStream := TMemoryStream.Create;
MemoryStream.Size := Reader.ReadInteger;
Reader.Read(MemoryStream.Memory^, MemoryStream.Size);
LoadFontFromStream(MemoryStream, FFont);
MemoryStream.Free;
end;
procedure TShapeData.ReadPicture(Reader: TReader);
var
PictureFormat: TPictureFormat;
MemoryStream: TMemoryStream;
begin
PictureFormat := TPictureFormat(Reader.ReadInteger);
if PictureFormat = pfUnknown then
Exit;
if FPicture = nil then FPicture := TPicture.Create;
MemoryStream := TMemoryStream.Create;
try
MemoryStream.Size := Reader.ReadInteger;
Reader.Read(MemoryStream.Memory^, MemoryStream.Size);
GetGraphicFormats.LoadFromStream(FPicture, MemoryStream);
finally
MemoryStream.Free;
end;
end;
procedure TShapeData.ReadStringList(var SL: TStringList; Reader: TReader);
var
i, Count: Integer;
begin
if SL <> nil then SL.Clear
else SL := TStringList.Create;
Count := Reader.ReadInteger;
for i := 0 to Count - 1 do
SL.Add(Reader.ReadWideString);
end;
procedure TShapeData.ReadTags(Reader: TReader);
begin
ReadStringList(FTags, Reader);
end;
procedure TShapeData.SetMultiLine(iPart, iPoint: Integer; const Value: TDoublePoint);
begin
FData[iPart, iPoint] := Value;
end;
procedure TShapeData.SetMultiLineCount(iPart: Integer; const Value: Integer);
begin
SetLength(FData[iPart], Value);
end;
procedure TShapeData.SetMultiPoint(iPoint: Integer; const Value: TDoublePoint);
begin
FData[0, iPoint] := Value;
end;
procedure TShapeData.SetMultiPointCount(const Value: Integer);
begin
SetLength(FData[0], Value);
end;
procedure TShapeData.SetPartCount(const Value: Integer);
begin
SetLength(FData, Value);
end;
procedure TShapeData.SetPoint(const Value: TDoublePoint);
begin
SetMultiLine(0, 0, Value);
end;
procedure TShapeData.SetRect(const Value: TDoubleRect);
begin
SetMultiLine(0, 0, Value.TopLeft);
SetMultiLine(0, 1, Value.BottomRight);
end;
procedure TShapeData.WriteData(Writer: TWriter);
var
iPart, iPoint: Integer;
begin
Writer.WriteFloat(FWidestPartBounds.Left);
Writer.WriteFloat(FWidestPartBounds.Top);
Writer.WriteFloat(FWidestPartBounds.Right);
Writer.WriteFloat(FWidestPartBounds.Bottom);
Writer.WriteFloat(FShapeCenter.X);
Writer.WriteFloat(FShapeCenter.Y);
Writer.WriteInteger(Integer(ShapeType));
Writer.WriteInteger(PartCount);
for iPart := 0 to PartCount - 1 do
begin
Writer.WriteInteger(MultiLineCount[iPart]);
for iPoint := 0 to MultiLineCount[iPart] - 1 do
with MultiLine[iPart, iPoint] do
begin
Writer.WriteFloat(X);
Writer.WriteFloat(Y);
end;
end;
case ShapeType of
stPicture:
begin
Writer.WriteBoolean(FConstrainProportions);
WritePicture(Writer);
end;
stLegend:
begin
WriteFont(Writer);
WriteStringList(FLegendText, Writer);
end;
stTemplate:
Writer.WriteString(FTemplateName);
end;
end;
procedure TShapeData.WriteFont(Writer: TWriter);
var
MemoryStream: TMemoryStream;
begin
MemoryStream := TMemoryStream.Create;
SaveFontToStream(MemoryStream, FFont);
Writer.WriteInteger(MemoryStream.Size);
Writer.Write(MemoryStream.Memory^, MemoryStream.Size);
MemoryStream.Free;
end;
procedure TShapeData.WritePicture(Writer: TWriter);
var
MemoryStream: TMemoryStream;
begin
if (FPicture.Graphic = nil) then
Writer.WriteInteger(Integer(pfUnknown))
else
begin
{ back compat }
Writer.WriteInteger(Integer(pfPicture));
MemoryStream := TMemoryStream.Create;
FPicture.Graphic.SaveToStream(MemoryStream);
Writer.WriteInteger(MemoryStream.Size);
Writer.Write(MemoryStream.Memory^, MemoryStream.Size);
MemoryStream.Free;
end;
end;
procedure TShapeData.WriteStringList(SL: TStringList; Writer: TWriter);
var
i: Integer;
begin
Writer.WriteInteger(SL.Count);
for i := 0 to SL.Count - 1 do
Writer.WriteWideString(SL[i]);
end;
procedure TShapeData.WriteTags(Writer: TWriter);
begin
WriteStringList(FTags, Writer);
end;
{ TMapToCanvasCoordinateConverter }
function TMapToCanvasCoordinateConverter.AspectRatio: Extended;
begin
Result := FXRange / FYRange;
end;
function TMapToCanvasCoordinateConverter.CanvasToMap(Canvas: TfrxPoint): TfrxPoint;
var
YTransformed, SinLat: Extended;
begin
Result.X := (Canvas.X - FOffsetX) * FXRange / FWidth + FXmin;
YTransformed := FYmaxTransformed - (Canvas.Y - FOffsetY) * FYRange / FHeight;
if MercatorProjection then
begin
SinLat := (Exp(YTransformed * Pi / 90) - 1) / (Exp(YTransformed * Pi / 90) + 1);
Result.Y := ArcSin(SinLat) * 180 / Pi;
end
else
Result.Y := YTransformed;
end;
function TMapToCanvasCoordinateConverter.ConvertMercator(Y: Extended): Extended;
var
SinLat: Extended;
begin
if Y > MaxMercatorLatitude then Y := MaxMercatorLatitude
else if Y < -MaxMercatorLatitude then Y := -MaxMercatorLatitude;
SinLat := Sin(Pi / 180 * Y);
Result := 90 / Pi * Ln((1 + SinLat) / (1 - SinLat));
end;
procedure TMapToCanvasCoordinateConverter.IgnoreShape;
begin
FShapeActive := False;
end;
procedure TMapToCanvasCoordinateConverter.IncludeRect(LayerRect: TfrxRect);
begin
with LayerRect do
if IsHasData then
begin
FXmin := Min(FXmin, Left);
FYmin := Min(FYmin, Top);
FXmax := Max(FXmax, Right);
FYmax := Max(FYmax, Bottom);
end
else
begin
FIsHasData := True;
FXmin := Left;
FYmin := Top;
FXmax := Right;
FYmax := Bottom;
end;
FXRange := FXmax - FXmin;
FYmaxTransformed := YTransform(FYmax);
FYRange := FYmaxTransformed - YTransform(FYmin);
end;
procedure TMapToCanvasCoordinateConverter.Init;
begin
FIsHasData := False;
FShapeActive := False;
end;
procedure TMapToCanvasCoordinateConverter.InitShape(AOffsetX, AOffsetY, AZoom: Extended; AShapeCenter: TfrxPoint);
begin
FShapeActive := MaxValue([Abs(AZoom - 1), Abs(AOffsetX), Abs(AOffsetY)]) > 1e-3;
if FShapeActive then
begin
FShapeOffset := frxPoint(AOffsetX, AOffsetY);
FShapeZoom := AZoom;
FShapeCenter := MapTransform(AShapeCenter);
end;
end;
function TMapToCanvasCoordinateConverter.MapTransform(P: TfrxPoint): TfrxPoint;
begin
Result := MapTransform(P.X, P.Y);
end;
function TMapToCanvasCoordinateConverter.MapTransform(X, Y: Extended): TfrxPoint;
begin
Result := frxPoint((X - FXmin) * FWidth / FXRange,
(FYmaxTransformed - YTransform(Y)) * FHeight / FYRange);
end;
procedure TMapToCanvasCoordinateConverter.ReadDFM(Stream: TStream);
var
Reader: TReader;
begin
Reader := TReader.Create(Stream, 4096);
Read(Reader);
Reader.Free;
end;
procedure TMapToCanvasCoordinateConverter.Read(Reader: TReader);
begin
FMercatorProjection := Reader.ReadBoolean;
FIsHasData := Reader.ReadBoolean;
FXmin := Reader.ReadFloat;
FYmin := Reader.ReadFloat;
FXmax := Reader.ReadFloat;
FYmax := Reader.ReadFloat;
FXRange := Reader.ReadFloat;
FYRange := Reader.ReadFloat;
FYmaxTransformed := Reader.ReadFloat;
FWidth := Reader.ReadFloat;
FHeight := Reader.ReadFloat;
FShapeActive := Reader.ReadBoolean;
FShapeZoom := Reader.ReadFloat;
FShapeOffset.X := Reader.ReadFloat;
FShapeOffset.Y := Reader.ReadFloat;
FShapeCenter.X := Reader.ReadFloat;
FShapeCenter.Y := Reader.ReadFloat;
end;
procedure TMapToCanvasCoordinateConverter.SetCanvasSize(CanvasSize: TfrxPoint);
begin
FWidth := CanvasSize.X;
FHeight := CanvasSize.Y;
end;
procedure TMapToCanvasCoordinateConverter.SetMercatorProjection(const Value: Boolean);
begin
FMercatorProjection := Value;
FYmaxTransformed := YTransform(FYmax);
FYRange := FYmaxTransformed - YTransform(FYmin);
end;
procedure TMapToCanvasCoordinateConverter.SetOffset(OffsetX, OffsetY: Extended);
begin
FOffsetX := OffsetX;
FOffsetY := OffsetY;
end;
function TMapToCanvasCoordinateConverter.ShapeTransform(P: TfrxPoint): TfrxPoint;
begin
Result := frxPoint(FShapeCenter.X + (P.X - FShapeCenter.X) * FShapeZoom + FShapeOffset.X,
FShapeCenter.Y + (P.Y - FShapeCenter.Y) * FShapeZoom + FShapeOffset.Y)
end;
function TMapToCanvasCoordinateConverter.Transform(X, Y: Extended): TfrxPoint;
begin
Result := MapTransform(X, Y);
if FShapeActive then
Result := ShapeTransform(Result);
if UseOffset then
begin
Result.X := Result.X + FOffsetX;
Result.Y := Result.Y + FOffSetY;
end;
end;
function TMapToCanvasCoordinateConverter.Transform(DoublePoint: TDoublePoint): TfrxPoint;
begin
with DoublePoint do
Result := Transform(X, Y);
end;
function TMapToCanvasCoordinateConverter.Transform(frxPoint: TfrxPoint): TfrxPoint;
begin
with frxPoint do
Result := Transform(X, Y);
end;
function TMapToCanvasCoordinateConverter.TransformOffset(frxPoint: TfrxPoint): TfrxPoint;
begin
with Transform(frxPoint) do
begin
Result.X := X + FOffsetX;
Result.Y := Y + FOffSetY;
end;
end;
function TMapToCanvasCoordinateConverter.TransformRect(R: TfrxRect): TfrxRect; // Flip Top <--> Bottom
var
P1, P2: TfrxPoint;
begin
P1 := Transform(R.Left, R.Bottom);
P2 := Transform(R.Right, R.Top);
Result := frxRect(P1.X, P1.Y, P2.X, P2.Y);
end;
procedure TMapToCanvasCoordinateConverter.WriteDFM(Stream: TStream);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Stream, 4096);
Write(Writer);
Writer.Free;
end;
procedure TMapToCanvasCoordinateConverter.Write(Writer: TWriter);
begin
Writer.WriteBoolean(FMercatorProjection);
Writer.WriteBoolean(FIsHasData);
Writer.WriteFloat(FXmin);
Writer.WriteFloat(FYmin);
Writer.WriteFloat(FXmax);
Writer.WriteFloat(FYmax);
Writer.WriteFloat(FXRange);
Writer.WriteFloat(FYRange);
Writer.WriteFloat(FYmaxTransformed);
Writer.WriteFloat(FWidth);
Writer.WriteFloat(FHeight);
Writer.WriteBoolean(FShapeActive);
Writer.WriteFloat(FShapeZoom);
Writer.WriteFloat(FShapeOffset.X);
Writer.WriteFloat(FShapeOffset.Y);
Writer.WriteFloat(FShapeCenter.X);
Writer.WriteFloat(FShapeCenter.Y);
end;
function TMapToCanvasCoordinateConverter.YTransform(Y: Extended): Extended;
begin
if MercatorProjection then
Result := ConvertMercator(Y)
else
Result := Y;
end;
{ TTaggedElement }
procedure TTaggedElement.AddTag(const stName, stValue: String);
begin
FTags.Add(stName + FTags.NameValueSeparator + stValue);
end;
constructor TTaggedElement.Create;
begin
FTags := TStringList.Create;
end;
destructor TTaggedElement.Destroy;
begin
FTags.Free;
end;
function TTaggedElement.IsHaveAllTags(LayerTags: TStrings): Boolean;
var
i: Integer;
begin
Result := False;
if LayerTags.Count <> 0 then
for i := 0 to LayerTags.Count - 1 do
if Tags.IndexOfName(LayerTags[i]) = -1 then
Exit;
Result := True;
end;
function TTaggedElement.IsHaveAnyTag(LayerTags: TStrings): Boolean;
var
i: Integer;
begin
Result := True;
if LayerTags.Count = 0 then
Exit;
for i := 0 to LayerTags.Count - 1 do
if Tags.IndexOfName(LayerTags[i]) <> -1 then
Exit;
Result := False;
end;
{ TfrxMapXMLReader }
procedure TfrxMapXMLReader.ChangeValueDlm(var Text: String);
var
i: Integer;
begin
for i := 1 to Length(Text) do
if Text[i] = '''' then
Text[i] := '"'
else if Text[i] = '"' then
Text[i] := '''';
end;
constructor TfrxMapXMLReader.Create(Stream: TStream);
begin
inherited Create(Stream);
FValueDlm := vdUnknown;
end;
procedure TfrxMapXMLReader.DefineValueDlm(Text: String);
var
PosApostrophe, PosQuote: Integer;
begin
PosApostrophe := Pos('''', Text);
PosQuote := Pos('"', Text);
if (PosApostrophe = 0) or
(PosQuote > 0) and (PosQuote < PosApostrophe) then
FValueDlm := vdQuote
else
FValueDlm := vdApostrophe;
end;
function TfrxMapXMLReader.IsFirstSlash(const InSt: String): Boolean;
begin
Result := InSt[1] = '/';
end;
function TfrxMapXMLReader.IsLastSlash(const InSt: String): Boolean;
var
Len: Integer;
begin
Len := Length(InSt);
Result := (Len > 0) and (InSt[Len] = '/');
end;
function TfrxMapXMLReader.IsReadMapXMLItem(Item: TfrxXMLItem): Boolean;
var
ChildItem: TfrxXMLItem;
begin
Result := IsReadMapXMLRootItem(Item);
if not Result or IsLastSlash(Item.Text) then
Exit;
repeat
ChildItem := TfrxXMLItem.Create;
if IsReadMapXMLItem(ChildItem) then
Item.AddItem(ChildItem)
else
begin
if IsFirstSlash(ChildItem.Name) then
Item.Value := ChildItem.Value;
ChildItem.Free;
Break;
end;
until False;
end;
function TfrxMapXMLReader.IsReadMapXMLRootItem(Item: TfrxXMLItem): Boolean;
var
sName, sText, sValue: String;
begin
repeat
ReadValuedItem(sName, sValue, sText);
until (Position >= Size) or (sName + sText <> '');
Item.Name := sName;
Item.Text := sText;
Item.Value := sValue;
Result := (sName <> '') and not IsFirstSlash(sName);
end;
procedure TfrxMapXMLReader.ReadValuedItem(var {$IFDEF Delphi12}NameS, ValueS{$ELSE}Name, Value{$ENDIF}, Text: String);
const
LenPiece = 512;
var
c: Byte;
curposName, curPosValue: Integer;
i: Integer;
{$IFDEF Delphi12}
Name, Value: AnsiString;
{$ENDIF}
begin
if EndOfStream then
Exit;
c := 0;
Text := '';
curposName := 0;
SetLength(Name, LenPiece);
curposValue := 0;
SetLength(Value, LenPiece);
while not EndOfStream do
begin
c := ReadFromBuffer;
if c = Ord('<') then
Break
else
begin
Inc(curposValue);
if curposValue > Length(Value) then
SetLength(Value, Length(Value) + LenPiece);
Value[curposValue] := AnsiChar(Chr(c));
end;
end;
while not EndOfStream do
begin
c := ReadFromBuffer;
if c = Ord('<') then
RaiseException
else if c = Ord('>') then
Break
else
begin
Inc(curposName);
if curposName > Length(Name) then
SetLength(Name, Length(Name) + LenPiece);
Name[curposName] := AnsiChar(Chr(c));
end;
end;
if c <> Ord('>') then
RaiseException;
SetLength(Name, curposName);
i := Pos(AnsiString(' '), Name);
if i <> 0 then
begin
Text := {$IFDEF Delphi12} UTF8Decode(Copy(Name, i + 1, curposName - i));
{$ELSE} Copy(Name, i + 1, curposName - i);
{$ENDIF}
SetLength(Name, i - 1);
end;
if FValueDlm = vdUnknown then
DefineValueDlm(Text);
if FValueDlm = vdApostrophe then
ChangeValueDlm(Text);
SetLength(Value, curposValue);
{$IFDEF Delphi12}
NameS := String(Name);
ValueS := UTF8Decode(Copy(Value, 1, curposValue));
{$ENDIF}
end;
{ TfrxMapXMLDocument }
procedure TfrxMapXMLDocument.DoneMapXMLFile;
begin
FMapXMLStreamReader.Free;
FTempStream := FMapXMLStream;
end;
procedure TfrxMapXMLDocument.InitMapXMLFile(const FileName: String);
begin
DeleteTempFile;
InitMapXMLInternal(TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite));
end;
procedure TfrxMapXMLDocument.InitMapXMLInternal(Stream: TStream);
begin
FMapXMLStream := Stream;
FMapXMLStreamReader := TfrxMapXMLReader.Create(FMapXMLStream);
Root.Clear;
Root.Offset := 0;
FMapXMLStreamReader.ReadHeader;
FOldVersion := FMapXMLStreamReader.OldFormat;
FMapXMLStreamReader.IsReadMapXMLRootItem(Root);
end;
procedure TfrxMapXMLDocument.InitMapXMLStream(Stream: TStream);
var
m: TMemoryStream;
begin
DeleteTempFile;
m := TMemoryStream.Create;
m.CopyFrom(Stream, Stream.Size);
m.Position := 0;
InitMapXMLInternal(m);
end;
function TfrxMapXMLDocument.IsReadItem(Item: TfrxXMLItem): Boolean;
begin
Result := FMapXMLStreamReader.IsReadMapXMLItem(Item);
end;
{ TfrxSumStringList }
function SumSortFunc(List: TStringList; Index1, Index2: Integer): Integer;
begin
Result := -Sign(Integer(List.Objects[Index1]) - Integer(List.Objects[Index2]));
end;
procedure TfrxSumStringList.AddSum(st: String);
var
i: Integer;
begin
Sorted := True;
if Find(st, i) then
Sum[i] := Sum[i] + 1
else
AddObject(st, Pointer(1));
end;
function TfrxSumStringList.GetSum(i: Integer): Integer;
begin
Result := Integer(Objects[i]);
end;
procedure TfrxSumStringList.SetSum(i: Integer; const Value: Integer);
begin
Objects[i] := Pointer(Value);
end;
procedure TfrxSumStringList.SortSum;
begin
Sorted := False;
CustomSort(SumSortFunc);
end;
{ TfrxClippingRect }
constructor TfrxClippingRect.Create;
begin
FActive := False;
end;
procedure TfrxClippingRect.Init(Active: Boolean; R: TfrxRect);
begin
FActive := Active;
FR := R;
end;
function TfrxClippingRect.IsCircleInside(Circle: TCircle): Boolean;
var
DistX, DistY: Extended;
begin
if FActive then
begin
DistX := Circle.X - Boundary(Circle.X, FR.Left, FR.Right);
DistY := Circle.Y - Boundary(Circle.Y, FR.Top, FR.Bottom);
Result := Sqr(DistX) + Sqr(DistY) < Sqr(Circle.Radius);
end
else
Result := True;
end;
function TfrxClippingRect.IsDiamondInside(Rect: TRect): Boolean;
var
PolyPoints: TPointArray;
begin
if FActive then
begin
SetLength(PolyPoints, 4);
with Rect do
begin
PolyPoints[0] := Point((Left + Right) div 2, Top);
PolyPoints[1] := Point(Right, (Top + Bottom) div 2);
PolyPoints[2] := Point((Left + Right) div 2, Bottom);
PolyPoints[3] := Point(Left, (Top + Bottom) div 2);
end;
Result := IsPolygonInside(PolyPoints);
end
else
Result := True;
end;
function TfrxClippingRect.IsPointInside(P: TfrxPoint): Boolean;
begin
if FActive then
Result := IsPointInRect(P, FR)
else
Result := True;
end;
function TfrxClippingRect.IsPolygonCover(PolyPoints: TPointArray): Boolean;
begin
Result := IsPointInPolygon(FR.Left, FR.Top, PolyPoints);
end;
function TfrxClippingRect.IsPolygonCover(
PolyPoints: TDoublePointArray): Boolean;
begin
Result := IsInsidePolygon(PolyPoints, frxPoint(FR.Left, FR.Top));
end;
function TfrxClippingRect.IsPolygonInside(
PolyPoints: TDoublePointArray): Boolean;
begin
Result := not FActive
or IsPolyLineInside(PolyPoints)
or IsSegmentInside(Segment(PolyPoints[0], PolyPoints[High(PolyPoints)]))
or IsPolygonCover(PolyPoints);
end;
function TfrxClippingRect.IsPolyLineInside(ShapeData: TShapeData; iPart: Integer): Boolean;
var
i: Integer;
begin
Result := True;
if FActive then
begin
for i := 0 to ShapeData.MultiLineCount[iPart] - 2 do
if IsSegmentInside(Segment(ShapeData.MultiLine[iPart, i], ShapeData.MultiLine[iPart, i + 1])) then
Exit;
Result := False;
end;
end;
function TfrxClippingRect.IsPolygonInside(PolyPoints: TPointArray): Boolean;
begin
Result := not FActive
or IsPolyLineInside(PolyPoints)
or IsSegmentInside(Segment(PolyPoints[0], PolyPoints[High(PolyPoints)]))
or IsPolygonCover(PolyPoints);
end;
function TfrxClippingRect.IsPolyLineInside(
PolyPoints: TDoublePointArray): Boolean;
var
i: Integer;
begin
Result := True;
if FActive then
begin
for i := 0 to High(PolyPoints) - 1 do
if IsSegmentInside(Segment(PolyPoints[i], PolyPoints[i + 1])) then
Exit;
Result := False;
end;
end;
function TfrxClippingRect.IsPolyLineInside(PolyPoints: TPointArray): Boolean;
var
i: Integer;
begin
Result := True;
if FActive then
begin
for i := 0 to High(PolyPoints) - 1 do
if IsSegmentInside(Segment(PolyPoints[i], PolyPoints[i + 1])) then
Exit;
Result := False;
end;
end;
function TfrxClippingRect.IsRectInside(Rect: TRect): Boolean;
var
PolyPoints: TPointArray;
begin
if FActive then
begin
SetLength(PolyPoints, 4);
with Rect do
begin
PolyPoints[0] := Point(Left, Top);
PolyPoints[1] := Point(Right, Top);
PolyPoints[2] := Point(Right, Bottom);
PolyPoints[3] := Point(Left, Bottom);
end;
Result := IsPolygonInside(PolyPoints);
end
else
Result := True;
end;
function TfrxClippingRect.IsSegmentInside(S: TSegment): Boolean;
begin
if FActive then
Result := IsPointInside(S.First) or IsPointInside(S.Second)
or IsSegmentsIntersect(S, Segment(FR.Left, FR.Top, FR.Right, FR.Top))
or IsSegmentsIntersect(S, Segment(FR.Left, FR.Top, FR.Left, FR.Bottom))
or IsSegmentsIntersect(S, Segment(FR.Left, FR.Bottom, FR.Right, FR.Bottom))
or IsSegmentsIntersect(S, Segment(FR.Right, FR.Top, FR.Right, FR.Bottom))
else
Result := True;
end;
function TfrxClippingRect.IsPolygonCover(ShapeData: TShapeData;
iPart: Integer): Boolean;
var
i1, i2: Integer;
begin
Result := False;
i2 := 0;
i1 := ShapeData.MultiLineCount[iPart] - 2;// Length(Poly) - 1;
while i1 >= 0 do
begin;
if not ((ShapeData.MultiLine[iPart, i1].X < FR.Left) xor (FR.Left <= ShapeData.MultiLine[iPart, i2].X)) then
if FR.Top - ShapeData.MultiLine[iPart, i1].Y < (FR.Left - ShapeData.MultiLine[iPart, i1].X) * (ShapeData.MultiLine[iPart, i2].Y - ShapeData.MultiLine[iPart, i1].Y) /
(ShapeData.MultiLine[iPart, i2].X - ShapeData.MultiLine[iPart, i1].X) then
Result := not Result;
i2 := i1;
i1 := i1 - 1;
end;
end;
function TfrxClippingRect.IsPolygonInside(ShapeData: TShapeData;
iPart: Integer): Boolean;
begin
Result := not FActive
or IsPolyLineInside(ShapeData, iPart)
or IsSegmentInside(Segment(ShapeData.MultiLine[iPart, 0], ShapeData.MultiLine[iPart, ShapeData.MultiLineCount[iPart] - 1]))
or IsPolygonCover(ShapeData, iPart);
end;
initialization
UseLog := False;
end.