lazmapviewer: Add geo distance calculation (patch by sstvmaster, https://forum.lazarus.freepascal.org/index.php/topic,12674.msg318879.html#msg318879).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6865 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2019-04-24 22:36:59 +00:00
parent 7b27af735d
commit 6ec131a022
4 changed files with 250 additions and 25 deletions

View File

@ -56,7 +56,7 @@ end;
procedure TGPSListViewer.Populate; procedure TGPSListViewer.Populate;
const const
GPS_FORMAT = '0.000000'; GPS_FORMAT = '0.00000°';
var var
i: Integer; i: Integer;
item: TListItem; item: TListItem;

View File

@ -1,10 +1,10 @@
object MainForm: TMainForm object MainForm: TMainForm
Left = 345 Left = 345
Height = 545 Height = 581
Top = 121 Top = 121
Width = 869 Width = 869
Caption = 'MainForm' Caption = 'MainForm'
ClientHeight = 545 ClientHeight = 581
ClientWidth = 869 ClientWidth = 869
OnCreate = FormCreate OnCreate = FormCreate
OnDestroy = FormDestroy OnDestroy = FormDestroy
@ -13,11 +13,11 @@ object MainForm: TMainForm
LCLVersion = '2.1.0.0' LCLVersion = '2.1.0.0'
object ControlPanel: TPanel object ControlPanel: TPanel
Left = 592 Left = 592
Height = 545 Height = 581
Top = 0 Top = 0
Width = 277 Width = 277
Align = alRight Align = alRight
ClientHeight = 545 ClientHeight = 581
ClientWidth = 277 ClientWidth = 277
TabOrder = 1 TabOrder = 1
object CbProviders: TComboBox object CbProviders: TComboBox
@ -145,11 +145,11 @@ object MainForm: TMainForm
Left = 16 Left = 16
Height = 15 Height = 15
Top = 23 Top = 23
Width = 46 Width = 43
BorderSpacing.Left = 16 BorderSpacing.Left = 16
BorderSpacing.Top = 4 BorderSpacing.Top = 4
BorderSpacing.Bottom = 8 BorderSpacing.Bottom = 8
Caption = 'Latitude:' Caption = 'Latitude'
ParentColor = False ParentColor = False
end end
object InfoPositionLongitude: TLabel object InfoPositionLongitude: TLabel
@ -195,11 +195,11 @@ object MainForm: TMainForm
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 9 Left = 9
Height = 66 Height = 66
Top = 185 Top = 181
Width = 259 Width = 259
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
AutoSize = True AutoSize = True
BorderSpacing.Top = 12 BorderSpacing.Top = 8
Caption = 'Center' Caption = 'Center'
ClientHeight = 46 ClientHeight = 46
ClientWidth = 255 ClientWidth = 255
@ -223,11 +223,11 @@ object MainForm: TMainForm
Left = 16 Left = 16
Height = 15 Height = 15
Top = 23 Top = 23
Width = 46 Width = 43
BorderSpacing.Left = 16 BorderSpacing.Left = 16
BorderSpacing.Top = 4 BorderSpacing.Top = 4
BorderSpacing.Bottom = 8 BorderSpacing.Bottom = 8
Caption = 'Latitude:' Caption = 'Latitude'
ParentColor = False ParentColor = False
end end
object InfoCenterLongitude: TLabel object InfoCenterLongitude: TLabel
@ -272,7 +272,7 @@ object MainForm: TMainForm
AnchorSideRight.Control = BtnSearch AnchorSideRight.Control = BtnSearch
Left = 9 Left = 9
Height = 23 Height = 23
Top = 264 Top = 334
Width = 190 Width = 190
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
BorderSpacing.Right = 8 BorderSpacing.Right = 8
@ -283,13 +283,13 @@ object MainForm: TMainForm
Text = 'New York' Text = 'New York'
end end
object BtnSearch: TButton object BtnSearch: TButton
AnchorSideTop.Control = GbCenterCoords AnchorSideTop.Control = GbScreenSize
AnchorSideTop.Side = asrBottom AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = GbCenterCoords AnchorSideRight.Control = GbCenterCoords
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 207 Left = 207
Height = 25 Height = 25
Top = 263 Top = 333
Width = 61 Width = 61
Anchors = [akTop, akRight] Anchors = [akTop, akRight]
AutoSize = True AutoSize = True
@ -305,7 +305,7 @@ object MainForm: TMainForm
AnchorSideRight.Control = BtnGoTo AnchorSideRight.Control = BtnGoTo
Left = 9 Left = 9
Height = 21 Height = 21
Top = 318 Top = 388
Width = 196 Width = 196
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
BorderSpacing.Top = 4 BorderSpacing.Top = 4
@ -325,7 +325,7 @@ object MainForm: TMainForm
AnchorSideTop.Side = asrBottom AnchorSideTop.Side = asrBottom
Left = 9 Left = 9
Height = 15 Height = 15
Top = 299 Top = 369
Width = 177 Width = 177
BorderSpacing.Top = 12 BorderSpacing.Top = 12
Caption = 'Select one of the found locations:' Caption = 'Select one of the found locations:'
@ -338,7 +338,7 @@ object MainForm: TMainForm
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 213 Left = 213
Height = 25 Height = 25
Top = 316 Top = 386
Width = 55 Width = 55
Anchors = [akTop, akRight] Anchors = [akTop, akRight]
AutoSize = True AutoSize = True
@ -354,7 +354,7 @@ object MainForm: TMainForm
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 8 Left = 8
Height = 52 Height = 52
Top = 380 Top = 450
Width = 260 Width = 260
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
AutoSize = False AutoSize = False
@ -368,7 +368,7 @@ object MainForm: TMainForm
AnchorSideTop.Side = asrBottom AnchorSideTop.Side = asrBottom
Left = 8 Left = 8
Height = 25 Height = 25
Top = 347 Top = 417
Width = 92 Width = 92
AutoSize = True AutoSize = True
BorderSpacing.Top = 8 BorderSpacing.Top = 8
@ -383,8 +383,8 @@ object MainForm: TMainForm
AnchorSideRight.Control = InfoBtnGPSPoints AnchorSideRight.Control = InfoBtnGPSPoints
AnchorSideRight.Side = asrBottom AnchorSideRight.Side = asrBottom
Left = 8 Left = 8
Height = 80 Height = 64
Top = 432 Top = 502
Width = 260 Width = 260
Anchors = [akTop, akLeft, akRight] Anchors = [akTop, akLeft, akRight]
AutoSize = False AutoSize = False
@ -498,7 +498,7 @@ object MainForm: TMainForm
AnchorSideTop.Control = BtnGPSPoints AnchorSideTop.Control = BtnGPSPoints
Left = 108 Left = 108
Height = 25 Height = 25
Top = 347 Top = 417
Width = 110 Width = 110
AutoSize = True AutoSize = True
BorderSpacing.Left = 8 BorderSpacing.Left = 8
@ -506,10 +506,110 @@ object MainForm: TMainForm
OnClick = BtnSaveToFileClick OnClick = BtnSaveToFileClick
TabOrder = 11 TabOrder = 11
end end
object GbScreenSize: TGroupBox
AnchorSideLeft.Control = GbCenterCoords
AnchorSideTop.Control = GbCenterCoords
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = GbCenterCoords
AnchorSideRight.Side = asrBottom
Left = 9
Height = 66
Top = 255
Width = 259
Anchors = [akTop, akLeft, akRight]
AutoSize = True
BorderSpacing.Top = 8
Caption = 'Viewport size'
ClientHeight = 46
ClientWidth = 255
TabOrder = 12
object LblViewportWidth: TLabel
AnchorSideLeft.Control = GbScreenSize
AnchorSideTop.Control = GbScreenSize
Left = 16
Height = 15
Top = 4
Width = 32
BorderSpacing.Left = 16
BorderSpacing.Top = 4
Caption = 'Width'
ParentColor = False
end
object LblViewportHeight: TLabel
AnchorSideLeft.Control = GbScreenSize
AnchorSideTop.Control = LblViewportWidth
AnchorSideTop.Side = asrBottom
Left = 16
Height = 15
Top = 23
Width = 36
BorderSpacing.Left = 16
BorderSpacing.Top = 4
BorderSpacing.Bottom = 8
Caption = 'Height'
ParentColor = False
end
object InfoViewportWidth: TLabel
AnchorSideTop.Control = GbScreenSize
AnchorSideRight.Control = GbScreenSize
AnchorSideRight.Side = asrBottom
Left = 205
Height = 15
Top = 4
Width = 34
Alignment = taRightJustify
Anchors = [akTop, akRight]
BorderSpacing.Top = 4
BorderSpacing.Right = 16
Caption = 'Label2'
ParentColor = False
end
object InfoViewportHeight: TLabel
AnchorSideTop.Control = InfoViewportWidth
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = GbScreenSize
AnchorSideRight.Side = asrBottom
Left = 205
Height = 15
Top = 23
Width = 34
Alignment = taRightJustify
Anchors = [akTop, akRight]
BorderSpacing.Top = 4
BorderSpacing.Right = 16
BorderSpacing.Bottom = 8
Caption = 'Label2'
ParentColor = False
end
end
object CbDistanceUnits: TComboBox
AnchorSideTop.Control = GbCenterCoords
AnchorSideTop.Side = asrBottom
AnchorSideRight.Control = GbCenterCoords
AnchorSideRight.Side = asrBottom
Left = 176
Height = 23
Top = 253
Width = 76
Anchors = [akTop, akLeft, akRight]
BorderSpacing.Top = 6
BorderSpacing.Right = 16
ItemHeight = 15
ItemIndex = 1
Items.Strings = (
'm'
'km'
'miles'
)
OnChange = CbDistanceUnitsChange
Style = csDropDownList
TabOrder = 13
Text = 'km'
end
end end
object MapView: TMapView object MapView: TMapView
Left = 0 Left = 0
Height = 545 Height = 581
Hint = 'Displays the map' Hint = 'Displays the map'
Top = 0 Top = 0
Width = 592 Width = 592
@ -523,6 +623,7 @@ object MainForm: TMainForm
UseThreads = True UseThreads = True
Zoom = 0 Zoom = 0
OnZoomChange = MapViewZoomChange OnZoomChange = MapViewZoomChange
OnChange = MapViewChange
OnDrawGpsPoint = MapViewDrawGpsPoint OnDrawGpsPoint = MapViewDrawGpsPoint
OnMouseMove = MapViewMouseMove OnMouseMove = MapViewMouseMove
OnMouseUp = MapViewMouseUp OnMouseUp = MapViewMouseUp

View File

@ -24,13 +24,19 @@ type
CbProviders: TComboBox; CbProviders: TComboBox;
CbUseThreads: TCheckBox; CbUseThreads: TCheckBox;
CbMouseCoords: TGroupBox; CbMouseCoords: TGroupBox;
CbDistanceUnits: TComboBox;
GbCenterCoords: TGroupBox; GbCenterCoords: TGroupBox;
GbScreenSize: TGroupBox;
InfoCenterLatitude: TLabel; InfoCenterLatitude: TLabel;
InfoViewportHeight: TLabel;
InfoCenterLongitude: TLabel; InfoCenterLongitude: TLabel;
InfoBtnGPSPoints: TLabel; InfoBtnGPSPoints: TLabel;
GPSPointInfo: TLabel; GPSPointInfo: TLabel;
InfoViewportWidth: TLabel;
Label8: TLabel; Label8: TLabel;
LblCenterLatitude: TLabel; LblCenterLatitude: TLabel;
LblViewportHeight: TLabel;
LblViewportWidth: TLabel;
LblPositionLongitude: TLabel; LblPositionLongitude: TLabel;
LblPositionLatitude: TLabel; LblPositionLatitude: TLabel;
InfoPositionLongitude: TLabel; InfoPositionLongitude: TLabel;
@ -53,11 +59,13 @@ type
ARect: TRect; State: TOwnerDrawState); ARect: TRect; State: TOwnerDrawState);
procedure CbProvidersChange(Sender: TObject); procedure CbProvidersChange(Sender: TObject);
procedure CbUseThreadsChange(Sender: TObject); procedure CbUseThreadsChange(Sender: TObject);
procedure CbDistanceUnitsChange(Sender: TObject);
procedure FormCreate(Sender: TObject); procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject); procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject); procedure FormShow(Sender: TObject);
procedure GeoNamesNameFound(const AName: string; const ADescr: String; procedure GeoNamesNameFound(const AName: string; const ADescr: String;
const ALoc: TRealPoint); const ALoc: TRealPoint);
procedure MapViewChange(Sender: TObject);
procedure MapViewDrawGpsPoint(Sender, ACanvas: TObject; APoint: TGpsPoint); procedure MapViewDrawGpsPoint(Sender, ACanvas: TObject; APoint: TGpsPoint);
procedure MapViewMouseMove(Sender: TObject; Shift: TShiftState; X, procedure MapViewMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer); Y: Integer);
@ -72,6 +80,7 @@ type
procedure ClearFoundLocations; procedure ClearFoundLocations;
procedure UpdateDropdownWidth(ACombobox: TCombobox); procedure UpdateDropdownWidth(ACombobox: TCombobox);
procedure UpdateLocationHistory(ALocation: String); procedure UpdateLocationHistory(ALocation: String);
procedure UpdateViewportSize;
public public
procedure ReadFromIni; procedure ReadFromIni;
@ -88,7 +97,7 @@ implementation
uses uses
LCLType, IniFiles, Math, FPCanvas, FPImage, IntfGraphics, LCLType, IniFiles, Math, FPCanvas, FPImage, IntfGraphics,
mvExtraData, mvEngine, mvExtraData,
gpslistform; gpslistform;
type type
@ -227,6 +236,11 @@ begin
MapView.UseThreads := CbUseThreads.Checked; MapView.UseThreads := CbUseThreads.Checked;
end; end;
procedure TMainForm.CbDistanceUnitsChange(Sender: TObject);
begin
UpdateViewPortSize;
end;
procedure TMainForm.ClearFoundLocations; procedure TMainForm.ClearFoundLocations;
var var
i: Integer; i: Integer;
@ -276,6 +290,11 @@ begin
CbFoundLocations.Items.AddObject(AName, P); CbFoundLocations.Items.AddObject(AName, P);
end; end;
procedure TMainForm.MapViewChange(Sender: TObject);
begin
UpdateViewportSize;
end;
procedure TMainForm.MapViewDrawGpsPoint(Sender, ACanvas: TObject; procedure TMainForm.MapViewDrawGpsPoint(Sender, ACanvas: TObject;
APoint: TGpsPoint); APoint: TGpsPoint);
const const
@ -332,12 +351,20 @@ var
i: Integer; i: Integer;
begin begin
rPt := MapView.Center; rPt := MapView.Center;
InfoCenterLongitude.Caption := Format('%.6f° = %s', [rPt.Lon, GPSToDMS(rPt.Lon)]);
InfoCenterLatitude.Caption := Format('%.6f° = %s', [rPt.Lat, GPSToDMS(rPt.Lat)]);
{
InfoCenterLongitude.Caption := Format('%.6f°', [rPt.Lon]); InfoCenterLongitude.Caption := Format('%.6f°', [rPt.Lon]);
InfoCenterLatitude.Caption := Format('%.6f°', [rPt.Lat]); InfoCenterLatitude.Caption := Format('%.6f°', [rPt.Lat]);
}
rPt := MapView.ScreenToLonLat(Point(X, Y)); rPt := MapView.ScreenToLonLat(Point(X, Y));
InfoPositionLongitude.Caption := Format('%.6f° = %s', [rPt.Lon, GPSToDMS(rPt.Lon)]);
InfoPositionLatitude.Caption := Format('%.6f° = %s', [rPt.Lat, GPSToDMS(rPt.Lat)]);
{
InfoPositionLongitude.Caption := Format('%.6f°', [rPt.Lon]); InfoPositionLongitude.Caption := Format('%.6f°', [rPt.Lon]);
InfoPositionLatitude.Caption := Format('%.6f°', [rPt.Lat]); InfoPositionLatitude.Caption := Format('%.6f°', [rPt.Lat]);
}
rArea.TopLeft := MapView.ScreenToLonLat(Point(X-DELTA, Y-DELTA)); rArea.TopLeft := MapView.ScreenToLonLat(Point(X-DELTA, Y-DELTA));
rArea.BottomRight := MapView.ScreenToLonLat(Point(X+DELTA, Y+DELTA)); rArea.BottomRight := MapView.ScreenToLonLat(Point(X+DELTA, Y+DELTA));
@ -349,7 +376,11 @@ begin
for i:=0 to gpsList.Count-1 do for i:=0 to gpsList.Count-1 do
if gpsList[i] is TGpsPoint then if gpsList[i] is TGpsPoint then
with TGpsPoint(gpsList[i]) do with TGpsPoint(gpsList[i]) do
L.Add(Format('%s' + Lineending + ' (lat=%.6f°, lon=%.6f°)', [Name, Lat, Lon])); L.Add(Format('%s' + Lineending + ' (lat=%.6f°=%s, lon=%.6f°=%s°)', [
Name, Lat, GPSToDMS(Lat), Lon, GPSToDMS(Lon)
]));
//L.Add(Format('%s' + Lineending + ' (lat=%.6f°, lon=%.6f°)', [Name, Lat, Lon]));
GPSPointInfo.Caption := L.Text; GPSPointInfo.Caption := L.Text;
finally finally
L.Free; L.Free;
@ -470,6 +501,30 @@ begin
CbLocations.Text := ALocation; CbLocations.Text := ALocation;
end; end;
procedure TMainForm.UpdateViewportSize;
begin
InfoViewportWidth.Caption := Format('%.2n %s', [
CalcGeoDistance(
MapView.GetVisibleArea.TopLeft.Lat,
MapView.GetVisibleArea.TopLeft.Lon,
MapView.GetVisibleArea.TopLeft.Lat,
MapView.GetVisibleArea.BottomRight.Lon,
TDistanceUnits(cbDistanceUnits.ItemIndex)
),
cbDistanceUnits.Items[cbDistanceUnits.ItemIndex]
]);
InfoViewportHeight.Caption := Format('%.2n %s', [
CalcGeoDistance(
MapView.GetVisibleArea.TopLeft.Lat,
MapView.GetVisibleArea.TopLeft.Lon,
MapView.GetVisibleArea.BottomRight.Lat,
MapView.GetVisibleArea.TopLeft.Lon,
TDistanceUnits(cbDistanceUnits.ItemIndex)
),
cbDistanceUnits.Items[cbDistanceUnits.ItemIndex]
]);
end;
procedure TMainForm.WriteToIni; procedure TMainForm.WriteToIni;
var var
ini: TCustomIniFile; ini: TCustomIniFile;

View File

@ -44,6 +44,8 @@ Type
TTileIdArray = Array of TTileId; TTileIdArray = Array of TTileId;
TDistanceUnits = (duMeters, duKilometers, duMiles);
{ TMapWindow } { TMapWindow }
TMapWindow = Record TMapWindow = Record
@ -161,6 +163,13 @@ Type
property OnZoomChange: TNotifyEvent read FOnZoomChange write FOnZoomChange; property OnZoomChange: TNotifyEvent read FOnZoomChange write FOnZoomChange;
end; end;
function CalcGeoDistance(Lat1, Lon1, Lat2, Lon2: double;
AUnits: TDistanceUnits = duKilometers): double;
function GPSToDMS(Angle: Double): string;
procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double);
implementation implementation
@ -1090,5 +1099,65 @@ begin
end; end;
//------------------------------------------------------------------------------
procedure SplitGps(AValue: Double; out ADegs, AMins: Double);
begin
AValue := abs(AValue);
AMins := frac(AValue) * 60;
ADegs := trunc(AValue);
end;
procedure SplitGps(AValue: Double; out ADegs, AMins, ASecs: Double);
begin
SplitGps(AValue, ADegs, AMins);
ASecs := frac(AMins) * 60;
AMins := trunc(AMins);
end;
function GPSToDMS(Angle: Double): string;
var
deg, min, sec: Double;
begin
SplitGPS(Angle, deg, min, sec);
Result := Format('%.0f° %.0f'' %.1f"', [deg, min, sec]);
end;
{ Returns the direct distance (air-line) between two geo coordinates
If latitude NOT between -90°..+90° and longitude NOT between -180°..+180°
the function returns -1.
Usage: FindDistance(51.53323, -2.90130, 51.29442, -2.27275, duKilometers);
}
function CalcGeoDistance(Lat1, Lon1, Lat2, Lon2: double;
AUnits: TDistanceUnits = duKilometers): double;
const
raduis_km: double = 6371.0; // generalized radius of earth
var
d_radians: double; // distance in radians
lat1r, lon1r, lat2r, lon2r: double;
begin
// Validate
if (Lat1 < -90.0) or (Lat1 > 90.0) then exit(-1);
if (Lon1 < -180.0) or (Lon1 > 180.0) then exit(-1);
if (Lat2 < -90.0) or (Lat2 > 90.0) then exit(-1);
if (Lon2 < -180.0) or (Lon2 > 180.0) then exit(-1);
// Turn lat and lon into radian measures
lat1r := (PI / 180.0) * Lat1;
lon1r := (PI / 180.0) * Lon1;
lat2r := (PI / 180.0) * Lat2;
lon2r := (PI / 180.0) * Lon2;
// calc
d_radians := arccos(Sin(lat1r) * sin(lat2r) + cos(lat1r) * cos(lat2r) * cos(lon1r - lon2r));
Result := EARTH_RADIUS * d_radians;
case AUnits of
duMeters: ;
duKilometers: Result := Result * 1E-3;
duMiles: Result := Result * 0.62137E-3;
end;
end;
end. end.