LazMapViewer: Added option TMapViewOption.mvoLatLonInDMS to show/edit Latitude and Longitude in deg-min-secs.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9270 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
alpine-a110 2024-03-05 16:57:24 +00:00
parent 78ce108950
commit 3f0fa39576
4 changed files with 228 additions and 21 deletions

View File

@ -222,6 +222,7 @@ function LatToStr(ALatitude: Double; DMS: Boolean; AFormatSettings: TFormatSetti
function LonToStr(ALongitude: Double; DMS: Boolean): String;
function LonToStr(ALongitude: Double; DMS: Boolean; AFormatSettings: TFormatSettings): String;
function TryStrToGps(const AValue: String; out ADeg: Double): Boolean;
function TryStrDMSToDeg(const AValue: String; out ADeg: Double): Boolean;
procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double);
@ -1648,6 +1649,138 @@ begin
ADeg := sgn * (abs(ADeg) + mins / 60 + secs / 3600);
end;
{ Convert Lat/Lon string to degrees.
Recognized formats:
Degrees, Minutes and Seconds:
DDD°MM'SS.S"
32°18'23.1"N 122°36'52.5"W
Degrees and Decimal Minutes:
DDD° MM.MMM'
32°18.385'N 122°36.875'W
Decimal Degrees:
DDD.DDDDD°
32.30642°N 122.61458°W
+32.30642 -122.61458
}
function TryStrDMSToDeg(const AValue: String; out ADeg: Double): Boolean;
const
NUMERIC_CHARS = ['0'..'9', '.', ',', '-', '+'];
WS_CHARS = [' '];
var
I, Len, N: Integer;
S: String;
D, Minutes, Seconds: Double;
R: Word;
Last, Neg: Boolean;
function EOL: Boolean; inline;
begin
Result := Len < I;
end;
procedure SkipWS; inline;
begin
while not EOL and (AValue[I] in WS_CHARS) do
Inc(I);
end;
function NextNum: String;
begin
Result := '';
SkipWS;
while not EOL and (AValue[I] in NUMERIC_CHARS) do
begin
if AValue[I] = ','
then Result := Result + '.'
else Result := Result + AValue[I];
Inc(I);
end;
end;
begin
Result := False;
ADeg := NaN;
Len := Length(AValue);
I := 1;
// Degrees
S := NextNum;
if S = '' then
Exit;
Val(S, ADeg, R);
if R > 0 then
Exit;
// It must be the only part if negative or fractional
Neg := ADeg < 0.0;
Last := Neg or (Frac(ADeg) > 0.0);
// Eat the degree symbol if present
if not EOL and (Utf8CodePointLen(@AValue[I], 2, False) = 2)
and (AValue[I] = #$c2) and (AValue[I + 1] = #$b0)
then
Inc(I, 2);
Minutes := 0.0;
Seconds := 0.0;
N := 1;
while not (EOL or Last) and (N < 3) do
begin
S := NextNum;
if S = '' then
Break
else
begin
Val(S, D, R);
// Invalid part or negative one
if (R > 0) or (D < 0.0) then
Exit;
// No more parts when fractional
Last := Frac(D) > 0.0;
if not EOL then
case AValue[I] of
'''': // Munutes suffix
begin
Minutes := D;
Inc(I); // Eat the '
end;
'"': // Seconds suffix
begin
Seconds := D;
Last := True; // Last part
Inc(I); // Eat the "
end;
otherwise
if N = 1
then Minutes := D
else Seconds := D;
end;
end;
Inc(N);
end;
// Merge parts
ADeg := ADeg + Minutes / 60 + Seconds / 3600;
// Check for N-S and E-W designators
SkipWS;
if not (EOL or Neg) and (AValue[I] in ['S', 's', 'W', 'w', 'N', 'n', 'E', 'e']) then
begin
if AValue[I] in ['S', 's', 'W', 'w']
then ADeg := -1 * ADeg;
Inc(I);
end;
SkipWS;
// It must be entirely consumed
Result := EOL;
end;
// https://stackoverflow.com/questions/73608975/pascal-delphi-11-formula-for-distance-in-meters-between-two-decimal-gps-point
function HaversineDist(Lat1, Lon1, Lat2, Lon2, Radius: Double): Double;
var

View File

@ -113,31 +113,13 @@ procedure TLayersPropertyEditForm.LoadFromFile(AFileName: String;
var
List: TGpsObjectList;
LBounds: TRealArea;
I: Integer;
procedure AddPoint(APoint: TGPSPoint);
begin
with ALayer.PointsOfInterest.Add as TPointOfInterest do
begin
Caption := APoint.Name;
Longitude := APoint.Lon;
Latitude := APoint.Lat;
Elevation := APoint.Elevation;
DateTime := APoint.DateTime;
end;
end;
begin
with TGpxReader.Create do
try
List := TGPSObjectList.Create;
try
LoadFromFile(AFileName, List, LBounds);
for I := 0 to Pred(List.Count) do
if List[I] is TGPSPoint then
AddPoint(TGPSPoint(List[I]))
else
{TODO};
ALayer.AssignFromGPSList(List);
finally
List.Free;
end;

View File

@ -35,7 +35,8 @@ Type
TMapViewOption =
(
mvoMouseDragging, // Allow dragging of the map with the mouse
mvoMouseZooming // Allow zooming into the map with the mouse
mvoMouseZooming, // Allow zooming into the map with the mouse
mvoLatLonInDMS // Use Degrees, Minutes and Seconds for Lat/Lon
);
TMapViewOptions = set of TMapViewOption;
@ -111,6 +112,7 @@ type
public
constructor Create(ACollection: TCollection); override;
destructor Destroy; override;
procedure AssignFromGPSList(AList: TGPSObjectList);
property MapView: TMapView read GetMapView;
property ComboLayer: TGPSComboLayer read FComboLayer;
published
@ -136,6 +138,7 @@ type
FLatitude: Double;
FLongitude: Double;
FView: TMapView;
function GetLatLonInDMS: Boolean;
procedure SetLatitude(AValue: Double);
procedure SetLongitude(AValue: Double);
procedure SetViewCenter;
@ -143,6 +146,7 @@ type
function GetOwner: TPersistent; override;
public
constructor Create(AView: TMapView);
property LatLonInDMS: Boolean read GetLatLonInDMS;
published
property Longitude: Double read FLongitude write SetLongitude;
property Latitude: Double read FLatitude write SetLatitude;
@ -159,6 +163,7 @@ type
FLongitude: Double;
FOnDrawPoint: TPointOfInterestDrawEvent;
FPoint: TGPSPointOfInterest;
function GetLatLonInDMS: Boolean;
function GetLayer: TMapLayer;
function GetMapView: TMapView;
function IsDateTimeStored: Boolean;
@ -178,6 +183,7 @@ type
destructor Destroy; override;
property MapView: TMapView read GetMapView;
property Layer: TMapLayer read GetLayer;
property LatLonInDMS: Boolean read GetLatLonInDMS;
published
property Longitude: Double read FLongitude write SetLongitude;
property Latitude: Double read FLatitude write SetLatitude;
@ -640,6 +646,11 @@ begin
else Result := Nil;
end;
function TPointOfInterest.GetLatLonInDMS: Boolean;
begin
Result := Assigned(MapView) and (mvoLatLonInDMS in MapView.Options);
end;
procedure TPointOfInterest.SetImageIndex(AValue: TImageIndex);
begin
if FImageIndex = AValue then Exit;
@ -737,6 +748,11 @@ begin
SetViewCenter;
end;
function TMapCenter.GetLatLonInDMS: Boolean;
begin
Result := Assigned(FView) and (mvoLatLonInDMS in FView.Options);
end;
procedure TMapCenter.SetViewCenter;
var
R: TRealPoint;
@ -877,6 +893,33 @@ begin
inherited Destroy;
end;
procedure TMapLayer.AssignFromGPSList(AList: TGPSObjectList);
procedure AddPoint(APoint: TGPSPoint);
begin
with PointsOfInterest.Add as TPointOfInterest do
begin
Caption := APoint.Name;
Longitude := APoint.Lon;
Latitude := APoint.Lat;
Elevation := APoint.Elevation;
DateTime := APoint.DateTime;
end;
end;
var
I: Integer;
begin
if not Assigned(AList) then
Exit;
PointsOfInterest.Clear;
for I := 0 to Pred(AList.Count) do
if AList[I] is TGPSPoint then
AddPoint(TGPSPoint(AList[I]))
else
{TODO};
end;
{ TMapLayers }
procedure TMapLayers.Update(Item: TCollectionItem);

View File

@ -63,12 +63,23 @@ type
procedure SetValue(const NewValue: AnsiString); override;
end;
{ TLatLonDMSPropertyEditor }
TLatLonDMSPropertyEditor = class(TFloatPropertyEditor)
private
function UseDMS: Boolean;
public
function GetValue: string; override;
procedure SetValue(const NewValue: AnsiString); override;
end;
procedure Register;
implementation
uses
Dialogs, IDEWindowIntf, mvMapViewer, mvGpsObj, mvLayersPropEditForm;
Dialogs, IDEWindowIntf, mvMapViewer, mvGpsObj, mvLayersPropEditForm, mvEngine,
StrUtils;
const
NONE = '(none)';
@ -110,6 +121,35 @@ begin
else inherited SetValue(NewValue);
end;
{ TLatLonDMSPropertyEditor }
function TLatLonDMSPropertyEditor.UseDMS: Boolean;
var
Inst: TPersistent;
begin
Inst := GetComponent(0);
Result := (Inst is TPointOfInterest) and TPointOfInterest(Inst).LatLonInDMS
or (Inst is TMapCenter) and TMapCenter(Inst).LatLonInDMS;
end;
function TLatLonDMSPropertyEditor.GetValue: string;
begin
if not UseDMS
then Result := inherited GetValue
else if StartsText('Lat', GetName)
then Result := LatToStr(GetFloatValue, True)
else Result := LonToStr(GetFloatValue, True);
end;
procedure TLatLonDMSPropertyEditor.SetValue(const NewValue: AnsiString);
var
Deg: Double;
begin
if UseDMS and TryStrDMSToDeg(NewValue, Deg)
then SetFloatValue(Deg)
else inherited SetValue(NewValue);
end;
{ TPointDateTimePropertyEditor }
function TPointDateTimePropertyEditor.GetValue: string;
@ -211,6 +251,15 @@ begin
TPointOfInterest, 'DateTime', TPointDateTimePropertyEditor);
RegisterPropertyEditor(TypeInfo(Double),
TPointOfInterest, 'Elevation', TPointElevationPropertyEditor);
RegisterPropertyEditor(TypeInfo(Double),
TPointOfInterest,'Latitude',TLatLonDMSPropertyEditor);
RegisterPropertyEditor(TypeInfo(Double),
TPointOfInterest,'Longitude',TLatLonDMSPropertyEditor);
RegisterPropertyEditor(TypeInfo(Double),
TMapCenter,'Latitude',TLatLonDMSPropertyEditor);
RegisterPropertyEditor(TypeInfo(Double),
TMapCenter,'Longitude',TLatLonDMSPropertyEditor);
end;