จากตอนที่ 2 จะเห็นโค๊ดที่ผม post 2 unit คือ GeoEllipsoids.pas และ GeoCompute.pas ถ้าสนใจก็ copy ไปวางที่ Text Editor แล้ว Save ชื่อไฟล์ตามที่ผม เราจะเริ่มต้นสร้าง New Project บน Lazarus จากนั้น Add file “GeoEllipsoids.pas” และ “GeoCompute.pas” เข้ามาในโปรเจคใหม่ที่สร้างขึ้น ผมทิ้งท้ายเรื่องสร้างโปรเจคใหม่ในตอนที่ 1 ก่อนจะกล่าวถึง 2 unit ในตอนที่ 2 กลับมาที่ Lazarus อีกครั้ง
Lazarus 0.9.29 (beta) บน windows
จะเห็นฟอร์ม unit1 นั้นเปล่าๆ ยังไม่ทีอะไร และ Project Inspector ด้านขวามือก็ยังว่างขึ้นเพียงแต่ project1.lpr และ unit1.pas เ่ท่านั้น ตรง component palette ยังมี component อีกมากมายของ Lazarus ที่ยังไม่ได้ติดตั้งเข้ามา ซึ่งจะอยู่ในไดเรคทอรี components ของ Lazarus สนใจตัวไหนก็ ใช้เมนูหลัก Packages > Open package file (.lpk) … ทำการ compile และ install ในตอนนี้ยังไม่ได้ใช้ตัวไหนเป็นพิเศษ จะใช้ standardd เท่านั้น เมื่อสร้างโปรเจคใหม่แล้ว จุดที่สำคัญที่ต้องตั้งเลยก็คือ OS และ CPU Platform ที่เมนูหลักเลือก Project > Compiler Options… จะเห็น dialog ให้ตั้งค่าดังรูปด้านล่าง
ตั้งค่า OS และ Platform
ที่ Project Inspector ให้คลิกที่เครื่องหมาย + เพื่อ Add file…
Add file to project
Add file ที่ผม post ไว้ตอนที่ 2 เข้ามา 2 ไฟล์ดังรูปด้านล่าง
Project Inspector หลังจากเพิ่มไฟล์
ที่ unit1 ทำการเพิ่ม object จาก standard palette เข้าไปตั้ง caption และชื่อดังรูปด้านล่าง
Create object on form.
โค๊ดข้างล่างเป็นของ unit1 เขียน event เชื่อมต่อกับ ojbect บนฟอร์มแล้ว
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, GeoEllipsoids, GeoCompute;
type
{ TForm1 }
TForm1 = class(TForm)
cmdEx1: TButton;
cmdEx2: TButton;
cmdCompute: TButton;
cmdClose: TButton;
cmbEllipsoids: TComboBox;
cmbHem: TComboBox;
Label7: TLabel;
txtZoneNo: TEdit;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Label6: TLabel;
txtE: TEdit;
txtLat: TEdit;
Label1: TLabel;
Label2: TLabel;
radComputeType: TRadioGroup;
txtLong: TEdit;
txtN: TEdit;
procedure cmdCloseClick(Sender: TObject);
procedure cmdComputeClick(Sender: TObject);
procedure cmdEx1Click(Sender: TObject);
procedure cmdEx2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure radComputeTypeClick(Sender: TObject);
private
{ private declarations }
FEllipses : TEllipsoidList;
public
{ public declarations }
end;
var
Form1: TForm1;
function Coordinate(const X, Y : double) : TCoordinate;
implementation
function Coordinate(const X, Y : double) : TCoordinate;
begin
result.X := X;
result.Y := Y;
end;
{ TForm1 }
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
//start with UTM to Geographic computation.
txtN.Enabled := true;
txtE.Enabled := true;
txtZoneNo.Enabled := true;
cmbHem.Enabled := true;
txtLat.Readonly := true;
txtLong.Readonly := true;
end;
procedure TForm1.cmdCloseClick(Sender: TObject);
begin
Close;
end;
procedure TForm1.cmdComputeClick(Sender: TObject);
var
utm2geo : TUTM2Geo;
geo2utm : TGeo2UTM;
ell : TEllipsoid;
x, y : double;
utmproj : TUTMProjection;
geocoor, utmcoor : TCoordinate;
begin
ell := FEllipses.FindEx(cmbEllipsoids.Text);
if (radComputeType.ItemIndex = 0) then //UTM to Geographic
begin
x := strtofloat(txtE.Text);
y := strtofloat(txtN.Text);
utmcoor := Coordinate(x, y);
utmproj.ZoneNo := strtoint(txtZoneNo.Text);
if (cmbHem.ItemIndex = 0)then
utmproj.LatHem := hemNorth
else
utmproj.LatHem := hemSouth;
try
utm2geo := TUTM2Geo.Create;
utm2geo.Ellipsoid := ell;
utm2geo.UTMProjection := utmproj;
utm2Geo.UTMCoordinate := utmcoor;
utm2Geo.Compute;
txtLat.Text := format('%11.8f', [utm2geo.GeoCoordinate.Y]);
txtLong.Text := format('%11.8f', [utm2geo.GeoCoordinate.X]);
finally
utm2geo.Free;
end;
end
else //Geographic to UTM
begin
x := strtofloat(txtLong.Text);
y := strtofloat(txtLat.Text);
geocoor := Coordinate(x, y);
try
geo2utm := TGeo2UTM.Create;
geo2utm.Ellipsoid := ell;
geo2utm.GeoCoordinate := geocoor;
geo2utm.Compute;
utmproj := geo2utm.UTMProjection;
txtN.Text := format('%11.3f', [geo2utm.UTMCoordinate.Y]);
txtE.Text := format('%11.3f', [geo2utm.UTMCoordinate.X]);
txtZoneNo.Text := format('%3d', [utmproj.ZoneNo]);
if (utmproj.LatHem = hemNorth) then
cmbHem.ItemIndex := 0
else
cmbHem.ItemIndex := 1;
finally
geo2utm.Free;
end;
end
end;
procedure TForm1.cmdEx1Click(Sender: TObject);
begin
radComputeType.ItemIndex := 0; //UTM to Geographic
txtN.Text := '3885802.712';
txtE.Text := '468327.864';
txtZoneNo.Text := '11'; //UTM Zone = 11
cmbHem.ItemIndex := 0; //North
txtLat.Text := '';
txtLong.Text := '';
end;
procedure TForm1.cmdEx2Click(Sender: TObject);
begin
radComputeType.ItemIndex := 1; //Geographic to UTM
txtLat.Text := '-37.6528211388889';
txtLong.Text := '143.926495527778';
txtN.Text := '';
txtE.Text := '';
txtZoneNo.Text := '';
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FEllipses.Free;
end;
procedure TForm1.radComputeTypeClick(Sender: TObject);
begin
if (radComputeType.ItemIndex = 0) then // UTM to Geographic
begin
cmdCompute.Caption := '<==';
txtN.Enabled := true;
txtE.Enabled := true;
txtZoneNo.Enabled := true;
cmbHem.Enabled := true;
txtN.SetFocus;
txtLat.Readonly := true;
txtLong.Readonly := true;
end
else
begin
cmdCompute.Caption := '==>'; //Geographic to UTM
txtN.Readonly := true;
txtE.Readonly := true;
txtZoneNo.Readonly := true;
cmbHem.Readonly := true;
txtLat.Enabled := true;
txtLong.Enabled := true;
txtLat.SetFocus;
end;
end;
initialization
{$I unit1.lrs}
end.
ดูที่ procedure TForm1.FormCreate(Sender: TObject); เป็น event ของ form1 ผมดึง Ellipsoids (FEllipsoids) ที่สร้างไว้เป็น object มา iterate ด้วย for loop ลงไปที่ combo box ชื่อ cmbEllipsoids เวลาผู้ใช้คลิกเลือก Ellipsoid จาก combo box จะได้เพียงแต่ text ออกมาเราจะใช้ text ค้นกลับไปที่ FEllipses( TEllipsoidList) เพื่อดึง object ที่แสดงรูปทรงของ Ellipsoid เมื่อผู้ใช้เลือกคลิกที่ radio group “Computation type” จะเลือกว่าจะคำนวณจาก UTM ไป Geographic หรือในทางกลับกัน อีเวนท์ของ radio group นี้อยู่ที่ procedure TForm1.radComputeTypeClick(Sender: TObject); จะได้ใช้ if clause เลือกการคำนวณถูกว่าจะใช้ class ไหน ระหว่าง TUTM2Geo หรือ TGeo2UTM เพื่อให้ดูง่ายยิ่งขึ้นผมเพิ่มตัวอย่างเพื่อจะใช้คำนวณดู โดยกำหนดอยู่ที่ button ชื่อ cmdEx1 และ cmdEx2 พร้อมทั้งเขียนอีเวนท์ให้ 2 procedure คือ procedure TForm1.cmdEx1Click(Sender: TObject); และ procedure TForm1.cmdEx2Click(Sender: TObject); ตามลำดับ
procedure TForm1.cmdEx1Click(Sender: TObject);
begin
radComputeType.ItemIndex := 0; //UTM to Geographic
txtN.Text := '3885802.712';
txtE.Text := '468327.864';
txtZoneNo.Text := '11'; //UTM Zone = 11
cmbHem.ItemIndex := 0; //North
txtLat.Text := '';
txtLong.Text := '';
end;
procedure TForm1.cmdEx2Click(Sender: TObject);
begin
radComputeType.ItemIndex := 1; //Geographic to UTM
txtLat.Text := '-37.6528211388889';
txtLong.Text := '143.926495527778';
txtN.Text := '';
txtE.Text := '';
txtZoneNo.Text := '';
end;
สุดท้าย button ที่เป็นรูปลูกศรชี้ไปซ้ายเมื่อเลือกคำนวณจาก UTM ไป Geographic และชี้ไปด้านขวาเมื่อเลือก Geographic ไป UTM เมื่อป้อนค่าพิกัดที่เหมาะสมแล้วทำการคลิกที่ button ก็จะไปปลุกอีเวนท์ ดังโค๊ดด้านล่าง
procedure TForm1.cmdComputeClick(Sender: TObject);
var
utm2geo : TUTM2Geo;
geo2utm : TGeo2UTM;
ell : TEllipsoid;
x, y : double;
utmproj : TUTMProjection;
geocoor, utmcoor : TCoordinate;
begin
ell := FEllipses.FindEx(cmbEllipsoids.Text);
if (radComputeType.ItemIndex = 0) then //UTM to Geographic
begin
x := strtofloat(txtE.Text);
y := strtofloat(txtN.Text);
utmcoor := Coordinate(x, y);
utmproj.ZoneNo := strtoint(txtZoneNo.Text);
if (cmbHem.ItemIndex = 0)then
utmproj.LatHem := hemNorth
else
utmproj.LatHem := hemSouth;
try
utm2geo := TUTM2Geo.Create;
utm2geo.Ellipsoid := ell;
utm2geo.UTMProjection := utmproj;
utm2Geo.UTMCoordinate := utmcoor;
utm2Geo.Compute;
txtLat.Text := format('%11.8f', [utm2geo.GeoCoordinate.Y]);
txtLong.Text := format('%11.8f', [utm2geo.GeoCoordinate.X]);
finally
utm2geo.Free;
end;
end
else //Geographic to UTM
begin
x := strtofloat(txtLong.Text);
y := strtofloat(txtLat.Text);
geocoor := Coordinate(x, y);
try
geo2utm := TGeo2UTM.Create;
geo2utm.Ellipsoid := ell;
geo2utm.GeoCoordinate := geocoor;
geo2utm.Compute;
utmproj := geo2utm.UTMProjection;
txtN.Text := format('%11.3f', [geo2utm.UTMCoordinate.Y]);
txtE.Text := format('%11.3f', [geo2utm.UTMCoordinate.X]);
txtZoneNo.Text := format('%3d', [utmproj.ZoneNo]);
if (utmproj.LatHem = hemNorth) then
cmbHem.ItemIndex := 0
else
cmbHem.ItemIndex := 1;
finally
geo2utm.Free;
end;
end
end;
จากโค๊ดด้านบนซึ่งจะทำหน้าที่คำนวณเมื่อผู้ใช้คลิกที่ปุ่มคำนวณรูปลูกศร ผม declare local variable ไว้ 2 ตัวแปร คือ utm2geo และ geo2utm ตามแต่จะเลือกคำนวณ มีตัวแปรที่สำคัญคือ ell : TEllipsoid เมื่อเริ่มต้นจะดึง object ของ TEllipsoid โดยใช้ชื่อที่ผู้ใช้คลิกที่ combo box “Ellipsoid” เป็นตัว index ในการค้นหา ดังโค๊ด ell := FEllipses.FindEx(cmbEllipsoids.Text); ตัวแปร utmcoor และ geocoor ดึงค่าพิกัดที่ผู้ใช้ป้อนลง text edit คงไม่ยากที่จะเข้าใจเพราะ class ที่ทำหน้าที่คำนวณ แยกออกไปต่างหากแล้ว ดูผลลัพธ์ที่ได้จากการคลิกปุ่ม Example 1 และ Example 2 ตามลำดับ
UTM to Geographic.
Geographic to UTM.
ผมตั้งใจจะรวมไฟล์โปรเจคนี้เป็น zip ไฟล์ แต่บริการแชร์ไฟล์ของ WordPress คือ Box.net ยังปิดบริการชั่วคราวครับ ตอนหน้าถ้าเป็น programming ด้วย Lazarus ผมอยากจะเขียนเรื่อง การคำนวณหาระยะทางและอะซิมัทบนรูปทรงรี (Geodesic distance & Azimuth on Ellipsoid) เมื่อ input เป็นค่าพิกัด Lat/Long ทั้ง 2 จุด (เป็นค่าพิกัด UTM ก็ได้แต่ต้องคำนวณเป็น Geographic ด้วย class ที่ผมเสนอในตอนนี้นั่นเอง) ผู้อ่านเคยสงสัยบ้างไหมครับว่าเครื่อง GPS เช่น Garmin เมื่อเราป้อน Waypoint ก็คือจุดที่มีค่าพิกัด ผู้ใช้สามารถเลือกป้อนเป็นค่า Latitude,Longitude ก็ได้ แต่ถ้าป้อนเป็นยูทีเอ็มจะสังเกตเห็นว่าเครื่อง GPS จะบังคับให้เราป้อนโซนของยูทีเอ็มเช่น 47P, 47Q อะไรประมาณนี้ (ตัวเลข P,Q เรียกว่า UTM Sub Zone เริ่มจากตัว C ที่ใกล้ขั้วโลกใต้ขึ้นไปหาตัว X ที่ขั้วโลกเหนือ) เครื่อง GPS จะฝังฟังก์ชั่นที่กล่าวนี้เพื่อหาระยะทางและอะซิมัทบน Ellipsoid และก็เช่นเคยสูตรพวกนี้นักคณิตศาสตร์ได้คิดกันมาก่อนแล้วก็มีท่านอื่นปรับปรุงมาเรื่อยๆให้ได้ความถูกต้องเพิ่มขึ้น
UTM sub zone.
คำถามคือทำไมไม่หาระยะทางและอะซิมัทจากค่าพิกัดยูทีเอ็มโดยตรง คำตอบคือหาได้ แต่ต้องอยู่บนโซนเดียวกัน แต่ค่าที่คำนวณจะผิดได้ ถ้าไม่มี UTM Zone No กำกับค่าพิกัดมาให้ เครื่อง GPS จึงบังคับให้เราป้อนค่าโชนดังที่ได้กล่าวไปแล้ว
Download sourcecode
Related
เป็นบทความที่ดีมาก อ่านแล้วได้ประโยชน์
ขอให้กำลังใจ “ช่างจวบ”
จากเพื่อนเก่า (มากๆ)
สมศักดิ์ สันประเสริฐ
sosanpt@yahoo.com
ขอบคุณครับ ศักดิ์…
ที่ให้กำลังใจ ก็ยังระลึกถึงอยู่เสมอ จำได้ว่าพบกันครั้งสุดท้ายที่งานสัมนาของอ.ชูเกียรติน่าจะปี 47 ตอนนี้ผมเข้าไปทำงานในพม่าเข้าๆออกๆอยู่แถวชายแดนจ.กาญจนบุรี ขอบคุณอีกครั้งที่แวะมาทักทายครับ