ตอนที่แล้วได้แนะนำสูตรที่จะใช้ในการคำนวณและแสดงยูนิต GeodesicCompute พร้อมทั้งยูนิต GeoEllipsoids ที่เคยแสดงไปแล้วเรื่องการแปลงค่าพิกัดระหว่าง UTM และ Geographic เปิด Lazarus คลิกที่เมนเมนู Project > New Project… คลิกเลือก Application คลิก OK
ตั้งค่า Compiler Options ก่อน
ผมขอย้ำอีกครั้งว่าเมื่อสร้าง Project ใหม่แล้วสิ่งแรกที่ต้องทำคือตั้ง Compiler Options ให้ตรงกับ Platform และ Widget ที่เราใช้อยู่ ที่เมนู Project > Compiler options… ที่ path เลือก LCL Widget เป็น win32/win64 ดังรูปด้านล่าง ที่ code คลิกเลือก Target OS เป็น win32/win64 และ Target CPU Family เป็น i386 ส่วน Target processor เลือกเป็น Default ดังรูปด้านล่าง
เพิ่มยูนิตเข้าไปใน Project
จากที่ได้กล่าวไปข้างต้นเรามี 2 ยูนิตที่เตรียมไว้แล้วคือ GeodesicCompute (geodesiccompute.pas) และ GeoEllipsoids (geoellipsoids.pas) ที่ Project Inspector คลิกที่เครื่องหมายบวกเพื่อเพิ่มยูนิต คลิกที่แท็ป Add files จากนั้นคลิกที่ Browse เพื่อเลือกไฟล์ที่เราเตรียมไว้
เมื่อเลือกไฟล์ได้แล้วจะเห็นหน้าตาของ Project Inspector ดังรูปด้านล่าง
คลิกที่เมนู Project > Save Project As… ตั้งชื่อเป็น Geodesics จะเห็นหน้าตาของ Lazarus และ project Geodesics ดังรูปด้านล่าง ต่อไปจะแปะพวก object ทั้งหลายเฃ่น Label, Edit, ComboBox, Button ลงไปและ ตั้งฃื่อ (Name) ดังรูปด้านล่าง
ส่วน Label และ GroupBox ใ้ส่ชื่อดังรูปด้านล่าง
โค๊ดของฟอร์ม Unit1
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, GeoEllipsoids, GeodesicCompute;
type
{ TForm1 }
TForm1 = class(TForm)
cmdEx1: TButton;
cmdCompute: TButton;
cmdEx2: TButton;
cmdClose: TButton;
cmbEllipsoids: TComboBox;
grbIn: TGroupBox;
grbOut: TGroupBox;
labin2: TLabel;
labin1: TLabel;
labout3: TLabel;
labout1: TLabel;
Label8: TLabel;
labout2: TLabel;
radCT: TRadioGroup;
txtIn1: TEdit;
txtOut1: TEdit;
txtLong1: TEdit;
txtLat1: TEdit;
GroupBox1: TGroupBox;
Label1: TLabel;
Label2: TLabel;
txtIn2: TEdit;
txtOut3: TEdit;
txtOut2: TEdit;
procedure cmdCloseClick(Sender: TObject);
procedure cmdComputeClick(Sender: TObject);
procedure cmdEx1Click(Sender: TObject);
procedure cmdEx2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure radCTClick(Sender: TObject);
private
{ private declarations }
FEllipses : TEllipsoidList;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{ TForm1 }
function Coordinate(const X, Y : double) : TCoordinate;
begin
result.X := X;
result.Y := Y;
end;
procedure TForm1.cmdCloseClick(Sender: TObject);
begin
Close;
end;
procedure TForm1.cmdComputeClick(Sender: TObject);
var
inverse : TGeodesicInverse;
direct : TGeodesicDirect;
ell : TEllipsoid;
x1, y1, x2, y2, az, ds : double;
geocoor : TCoordinate;
begin
ell := FEllipses.FindEx(cmbEllipsoids.Text);
if (radCT.ItemIndex = 0) then
begin
x1 := strtofloat(txtlong1.Text);
y1 := strtofloat(txtlat1.Text);
x2 := strtofloat(txtIn2.Text);
y2 := strtofloat(txtIn1.Text);
try
inverse := TGeodesicInverse.Create;
inverse.Ellipsoid := ell;
inverse.GeoCoordinate1 := Coordinate(x1, y1);
inverse.GeoCoordinate2 := Coordinate(x2, y2);
inverse.Compute;
txtOut1.Text := format('%11.8f', [inverse.InitialAzimuth]);
txtOut2.Text := format('%11.8f', [inverse.FinalAzimuth]);
txtOut3.Text := format('%10.3f m.', [inverse.Distance]);
finally
inverse.Free;
end;
end
else if (radCT.ItemIndex = 1) then //Direct
begin
x1 := strtofloat(txtlong1.Text);
y1 := strtofloat(txtlat1.Text);
ds := strtofloat(txtIn2.Text);
az := strtofloat(txtIn1.Text);
try
direct := TGeodesicDirect.Create;
direct.Ellipsoid := ell;
direct.GeoCoordinate1 := Coordinate(x1, y1);
direct.Azimuth := az;
direct.Distance := ds;
direct.Compute;
x2 := direct.GeoCoordinate2.X;
y2 := direct.GeoCoordinate2.Y;
txtOut1.Text := format('%11.8f', [y2]);
txtOut2.Text := format('%11.8f', [x2]);
finally
direct.Free;
end;
end;
end;
procedure TForm1.cmdEx1Click(Sender: TObject);
begin
radCT.ItemIndex := 0; //Geodesic Inverse
txtLat1.Text := '53.150555556';
txtLong1.Text :='-1.844444444';
txtIn1.Text := '52.205277778';
txtIn2.Text :='-0.142500000';
txtOut1.Text := '';
txtOut2.Text := '';
txtOut3.Text := '';
end;
procedure TForm1.cmdEx2Click(Sender: TObject);
begin
radCT.ItemIndex := 1; //Geodesic Direct
txtLat1.Text := '-37.9510334';
txtLong1.Text := '144.424868';
txtIn1.Text :='306.8681583';
txtIn2.Text :='54972.271';
txtOut1.Text := '';
txtOut2.Text := '';
txtOut3.Text := '';
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i : integer;
begin
FEllipses := TEllipsoidList.Create;
for i := 0 to FEllipses.Count - 1 do
begin
cmbEllipsoids.Items.Add(TEllipsoid(FEllipses.Objects[i]).EllipsoidName);
end;
cmbEllipsoids.ItemIndex := FEllipses.Count - 1; //Default with WGS84
end;
procedure TForm1.radCTClick(Sender: TObject);
begin
if (radCT.ItemIndex = 0) then //Inverse.
begin
grbin.Caption := 'Point 2';
labin1.Caption := 'Latitude : ';
labin2.Caption := 'Longitude : ';
grbout.Caption := 'Azimuth && Distance';
labout1.Caption := 'Initial Azimuth : ';
labout2.Caption := 'Final Azimuth : ';
labout3.Show;
txtout3.Show;
labout3.Caption := 'Distance : ';
end
else if (radCT.ItemIndex = 1) then
begin
grbin.Caption := 'Azimuth && Distance';
labin1.Caption := 'Azimuth : ';
labin2.Caption := 'Distance : ';
grbout.Caption := 'Point 2';
labout1.Caption := 'Latitude : ';
labout2.Caption := 'Longitude : ';
labout3.Hide;
txtout3.Hide;
end;
end;
initialization
{$I unit1.lrs}
end.
อธิบายโค๊ด
ที่นี้มาลองไล่ดูโค๊ดเฉพาะบางส่วนที่สำคัญ เริ่มแรกตรงเมธอด FormCreate
procedure TForm1.FormCreate(Sender: TObject);
var
i : integer;
begin
FEllipses := TEllipsoidList.Create;
for i := 0 to FEllipses.Count - 1 do
begin
cmbEllipsoids.Items.Add(TEllipsoid(FEllipses.Objects[i]).EllipsoidName);
end;
cmbEllipsoids.ItemIndex := FEllipses.Count - 1; //Default with WGS84
end;
เ่ริ่มจากการสร้าง object สำหรับเก็บทรงรี TEllipsoidList นั้นออกแบบมาคล้ายๆกับ Collection เมื่อสร้าง Object เรียบร้อยแล้วก็นำมาใ่ส่ให้ ComboBox ชื่อ cmbEllipsoids เ้สร็จแล้วให้สถานะของทรงรีเป็น WGS84 (ทรงรี WGS84 จะอยู่ท้ายสุดใน list) ต่อไปผมสร้าง Event เมื่อคลิกด้วยเมาส์เพื่อโยงให้ radio group box ชื่อ radCT (property Items ผมใส่ string ให้ 2 string คือ Geodesic Inverse และ Geodesic Direct ตามลำดับ) ตัว radCT จะทำหน้าที่ให้ผู้ใช้โปรแกรมเลือกว่าจะคำนวณแบบไหน เมื่อเลือกแบบใดแบบหนึ่งโปรแกรมจะเปลี่ยน label ให้สอดคล้อง input และ output
procedure TForm1.radCTClick(Sender: TObject);
begin
if (radCT.ItemIndex = 0) then //Inverse.
begin
grbin.Caption := 'Point 2';
labin1.Caption := 'Latitude : ';
labin2.Caption := 'Longitude : ';
grbout.Caption := 'Azimuth && Distance';
labout1.Caption := 'Initial Azimuth : ';
labout2.Caption := 'Final Azimuth : ';
labout3.Show;
txtout3.Show;
labout3.Caption := 'Distance : ';
end
else if (radCT.ItemIndex = 1) then
begin
grbin.Caption := 'Azimuth && Distance';
labin1.Caption := 'Azimuth : ';
labin2.Caption := 'Distance : ';
grbout.Caption := 'Point 2';
labout1.Caption := 'Latitude : ';
labout2.Caption := 'Longitude : ';
labout3.Hide;
txtout3.Hide;
end;
end;
เพื่อให้การใช้โปรแกรมนั้นง่าย ผมจะใส่ตัวอย่างข้อมูสำหรับป้อนเข้า ไว้สองตัวอย่างคือคำนวณแบบ Inverse (Example 1) และคำนวณแบบ Direct (Example 2) เมื่อผู่ใช้ทำการคลิกที่ปุ่ม Example 1 จะเรียก Event ที่สร้างไว้คือ cmdEx1Click ส่วนปุ่ม Example 2 จะเรียก event cmdEx2Click
procedure TForm1.cmdEx1Click(Sender: TObject);
begin
radCT.ItemIndex := 0; //Geodesic Inverse
txtLat1.Text := '53.150555556';
txtLong1.Text :='-1.844444444';
txtIn1.Text := '52.205277778';
txtIn2.Text :='-0.142500000';
txtOut1.Text := '';
txtOut2.Text := '';
txtOut3.Text := '';
end;
procedure TForm1.cmdEx2Click(Sender: TObject);
begin
radCT.ItemIndex := 1; //Geodesic Direct
txtLat1.Text := '-37.9510334';
txtLong1.Text := '144.424868';
txtIn1.Text :='306.8681583';
txtIn2.Text :='54972.271';
txtOut1.Text := '';
txtOut2.Text := '';
txtOut3.Text := '';
end;
สุดท้ายก็มาดูที่เมธอดสำคัญคือ cmdComputeClick สร้างไว้เพื่อดัก event เมื่อผู้ใช้คลิกที่ปุ่ม Compute
procedure TForm1.cmdComputeClick(Sender: TObject);
var
inverse : TGeodesicInverse;
direct : TGeodesicDirect;
ell : TEllipsoid;
x1, y1, x2, y2, az, ds : double;
geocoor : TCoordinate;
begin
ell := FEllipses.FindEx(cmbEllipsoids.Text);
if (radCT.ItemIndex = 0) then
begin
x1 := strtofloat(txtlong1.Text);
y1 := strtofloat(txtlat1.Text);
x2 := strtofloat(txtIn2.Text);
y2 := strtofloat(txtIn1.Text);
try
inverse := TGeodesicInverse.Create;
inverse.Ellipsoid := ell;
inverse.GeoCoordinate1 := Coordinate(x1, y1);
inverse.GeoCoordinate2 := Coordinate(x2, y2);
inverse.Compute;
txtOut1.Text := format('%11.8f', [inverse.InitialAzimuth]);
txtOut2.Text := format('%11.8f', [inverse.FinalAzimuth]);
txtOut3.Text := format('%10.3f m.', [inverse.Distance]);
finally
inverse.Free;
end;
end
else if (radCT.ItemIndex = 1) then //Direct
begin
x1 := strtofloat(txtlong1.Text);
y1 := strtofloat(txtlat1.Text);
ds := strtofloat(txtIn2.Text);
az := strtofloat(txtIn1.Text);
try
direct := TGeodesicDirect.Create;
direct.Ellipsoid := ell;
direct.GeoCoordinate1 := Coordinate(x1, y1);
direct.Azimuth := az;
direct.Distance := ds;
direct.Compute;
x2 := direct.GeoCoordinate2.X;
y2 := direct.GeoCoordinate2.Y;
txtOut1.Text := format('%11.8f', [y2]);
txtOut2.Text := format('%11.8f', [x2]);
finally
direct.Free;
end;
end;
end;
จากโค๊ดด้านบน เมื่อผู้ใช้เลือกแบบการคำนวณโดยการคลิกที่ radio group โปรแกรมเราจะเลือกการคำนวณให้ที่เหมาะสม โดยจะมี 2 ตัวแปรสำคํญคือ direct เรา assign จาก class ชื่อ TGeodesicDirect และตัวแปรอีกตัวคือ inverse โดย derive มาจาก class ชื่อ TGeodesicInverse ลองดู source code ก็ไม่มีอะไรยากเลย เริ่มต้นจากการสร้าง object => inverse หรือ direct จากนั้นก็เอาทรงรีมา assign ให้จากผู้ใช้คลิกเลือกทรงรีที่ ComboBox ต่อไปก็ input ค่าพิกัด Geographic ให้ 2 จุด (ถ้าคำนวณ inverse) หรือป้อนค่าพิกัดให้ 1 จุด แล้วป้อน ระยะทาง และ อะซิมัท จากนั้นจะเรียกเมธอด Compute และส่งค่าผลลัพธ์มาทาง property
การคำนวณแบบ Inverse
เมื่อทุกสิ่งทุกอย่างพร้อมกดปุ่มคีย์บอร์ด F9 เพื่อให้ Lazarus ทำการ compile และ build เราตั้งชื่อ project เราว่า Geodesics เมื่อ build แล้วจะได้ไฟล์ Geodesics.exe ส่วนใน Linux จะไม่มี extension ให้ เวลารันถ้าใช้ command ใน linux จะเป็นดังนี้ $./geodesic s (พิมพ์จุดและเครื่องหมาย fore slash แล้วตามด้วยชื่อโปรแกรม) ตัวอย่างเมื่อรันโปรแกรม คลิกที่ปุ่ม Example 1 แล้วคลิก Compute เพื่อทำการคำนวณ จะได้ผลลัพธ์ดังรูปด้านล่าง
สังเกตว่าที่เราทำการคำนวณคือระยะทางและอะซิมัทไปตามเส้น Geodesic ซึ่งเป็นเส้นโค้งอะซิมัทจะค่อยๆเปลี่ยนไปตามเส้นโค้ง ดังน้น Initial Azimuth จึงเป็นค่าอะซิมัทเริ่มต้น และ Final Azimuth คือค่าอะซิมัทสุดท้ายที่เส้น geodesic ไปแตะจุดที่สองพอดี
การคำนวณแบบ Direct
รันโปรแกรม คลิกที่ปุ่ม Example 2 แล้วคลิก Compute เพื่อทำการคำนวณ จะได้ผลลัพธ์ดังรูปด้านล่าง
ข้อจำกัดของโปรแกรม
เนื่องจากเป็นโปรแกรมตัวอย่างที่ให้ดูกันง่ายๆ จึงไม่มีการดักข้อมูล error จากการพิมพ์ใส่ข้อมูล ที่ EditBox เช่นถ้าผู้ใช้ป้อนแทนที่จะเป็นตัวเลขแต่ไปใส่อักขระอื่นแทนก็จะ error และไม่มีการดักจับค่าพิกัด 2 จุดจะต้องไม่เท่ากัน ถ้าเท่ากันก็จะ error เช่นเดียวกันขณะรันโปรแกรม ก็ขอจบกันไว้เพียงเท่านี้ ตอนหน้าจะมาว่าเรื่องฐานข้อมูลฉบับกระเป๋าเล้กพริกขี้หนู SQLite ส่วนในตอนต่อๆไปอาจจะเป็นฐานข้อมูลเจ้านกไฟ FireBird
Download sourcecode
Related