Archive
แนะนำการใช้ฐานข้อมูล SQLite กับ Lazarus
ข้อดีของ SQLite
- พัฒนาโดย D. Richard Hipp ด้วยภาษา C จำนวนโค๊ดรวมๆแล้วประมาณสามหมื่นกว่าบรรทัด ซึ่งผู้พัฒนาได้รับคำชมว่าเป็นผู้ที่เข้าใจในวิทยาการด้านคอมพิวเตอร์อย่างลึกซึ้ง
- สำหรับ SQLite น่าจะเป็นฐานข้อมูลที่นิยมใช้กันมากที่สุดในโลก เนื่องจาก เล็ก เร็ว แรง และที่สำคัญมากคือ เสถียร และข้อดีอีกที่ไม่พูดไม่ได้คือ ฟรีและ cross-platform เป็นฐานข้อมูลที่จัดเก็บในไฟล์เดียว ไม่ต้องมีฝั่ง Server ไม่ต้องตั้งค่าใดๆให้ยุ่งยาก
- สามารถใช้ภาษาโปรแกรมได้เกือบทุกภาษาในโลกนี้ โดยเฉพาะ Lazarus จะมี component ที่ติดตั้งมาให้พร้อมใช้งานได้เลย
สถานการณ์ที่เหมาะและไม่เหมาะสำหรับ SQLite
- ถ้าใช้เป็นฐานข้อมูลสำหรับ website ที่คนเข้าไม่มากนักก็ใช้ได้ดี หรือเป็นฐานข้อมูลสำหรับโปรแกรมสำหรับเครื่อง PDA ก็สะดวกเพราะมีขนาดเล็ก สำหรับความคิดของผมแล้วฐานข้อมูลไหนก็ตามที่ใช้ Microsoft Access ใช้ SQLite แทนได้ทั้งนั้น แถมดีกว่าทุกๆอย่าง
- สถานการณ์ที่ไม่เหมาะได้แก่โปรแกรม Client/Server ที่ระบบข้อมูลมีการส่งผ่านข้อมูลสูงมาก เช่น website ที่มีคนเข้ามาก ควรหันไปใช้ RDBMS ตัวใหญ่ๆจะดีกว่าเช่น MySQL, MS SQL
Download และ ติดตั้ง
- สำหรับ windows ไปดาวน์โหลดไบนารีได้ที่ http://www.sqlitedll.org/sqlite-3_6_19.zip (ขณะที่เขียน blog นี้เป็น version 3.6.19) เมื่อแตกไฟล์ zip ออกมาจะได้ sqlite3.dll และไฟล์ sqlite3.def ให้ copy ไปไว้ที่ c:\windows\systems32 หรือ copy ไปไว้ที่โฟลเดอร์ของโปรแกรมที่กำลังพัฒนาด้วย lazarus
- ถ้าต้องการ tools สำหรับ admin ใช้บน command line เพื่อจัดการฐานข้อมูลก็ดาวน์โหลดได้ที่ http://www.sqlite.org/sqlite-3_6_19.zip
- สำหรับ Linux ที่ผมใช้อยู่คือ Ubuntu และ PCLinuxOS ใช้คำสั่งเดียวกันคือ
$ sudo apt-get install sqlite
สุดยอด Admin Tools สำหรับ SQLite บนวินโดส์
- ลองมาหลายอย่างแล้วไม่มี tools ตัวไหนดีเท่ากับตัวนี้ ชื่อโปรแกรมก็คือ SQLite Administrator สมกับที่ผู้พัฒนาเรียกโปรแกรมเขาว่า power tool สามารถออกแบบ สร้าง บริหารฐานข้อมูล SQLite ได้
- ดาวน์โหลดได้ที่ http://download.orbmu2k.de/files/sqliteadmin.zip
- โปรแกรมมีขนาดเล็กไม่ต้องติดตั้ง unzip ไปวางไว้ตรงโฟลเดอร์ไหนก็ได้ มารันโปรแกรมกันดู

เริ่มต้นสร้างฐานข้อมูลด้วย SQLite Administrator
- การสร้างฐานข้อมูล SQLite ใหม่สามารถทำได้หลายวิธี วิธีที่ยากกว่า(แต่ก็ไม่ยากเท่าไหร่) ก็คือสร้างด้วยโค๊ด ที่เราสนใจคือสร้างด้วย Lazarus โดยเรียกใช้ผ่านภาษา sql ส่วนวิธีที่ง่ายกว่า ก้สร้างด้วยโปรแกรม SQLite Administrator ที่แนะนำกันนี้แหละ
- ตัวอย่างที่จะแนะนำกันได้แก่สร้างฐานข้อมูลเพื่อเก็บรูปทรงรีเพื่อมาใช้แทนยูนิตของ Lazarus ที่ผมเสนอไปหลายตอนแล้วคือ GeoEllipsoids (geoellipsoids.pas) ผมจะเริ่มจาก text file (.csv) ชื่อไฟล์ Ellipsoids.csv ที่เก็บรูปทรงรีดังลิสต์ด้านล่าง (ถ้าผู้อ่านสนใจก็ลอง copy ไปวางที่ text editor แล้วเซฟชื่อเป็น ellipsoids.csv)
Airy 1830,AA,6377563.396,6356256.909,299.3249646
Modified Airy,AM,6377340.189,6356034.448,299.3249646
Australian National,AN,6378160,6356774.719,298.25
Bessel 1841(Namibia),BN,6377483.865,6356165.383,299.1528128
Bessel 1841,BR,6377397.155,6356078.963,299.1528128
Clarke 1866,CC,6378206.4,6356583.8,294.9786982
Clarke 1880,CD,6378249.145,6356514.87,293.465
Everest 1830,EA,6377276.345,6356075.413,300.8017
Everest (E. Malasia & Brunei),EB,6377298.556,6356097.55,300.8017
Everest 1956,EC,6377301.243,6356100.228,300.8017
Everest 1969,ED,6377295.664,6356094.668,300.8017
Everest 1948,EE,6377304.063,6356103.039,300.8017
Everest (Pakistan),EF,6377309.613,6356109.571,300.8017
Mod. Fischer 1960(South Asia),FA,6378155,6356773.32,298.3
Helmert 1906,HE,6378200,6356818.17,298.3
Hough 1960,HO,6378270,6356794.343,297
Indonesian 1974,ID,6378160,6356774.504,298.247
International 1924,IN,6378388,6356911.946,297
Krassovsky 1940,KA,6378245,6356863.019,298.3
GRS 80,RF,6378137,6356752.314,298.2572221
South American 1969,SA,6378160,6356774.719,298.25
WGS 72,WD,6378135,6356750.52,298.26
WGS 84,WE,6378137,6356752.314,298.2572236
- ที่เมนเมนูของโปรแกรมคลิกที่ Database > New… หรือคลิกที่ icon “Create Database” ก็ได้ ป้อนชื่อฐานข้อมูลเป็น Datums.s3db แล้วเซฟไว้
สร้าง Table ใหม่
- ตอนนี้เราได้ฐานข้อมูลมาแล้วหนึ่งไฟล์ ต่อไปเราจะสร้าง Table เพื่อเก็บรูปทรงรีจะตั้งชื่อ Table นี้ว่า Ellipsoids คลิกขวาที่ Tables > Create Table ดังรูปด้านล่าง

- จะเห็น dialog ขึ้นมา ป้อนชื่อ table แล้วคลิกที่ Add field ป้อนชื่อ field , field type ดังรูปด้านล่าง

- ทำการ Add field เข้าไปอีกให้ครบ

- จากรูปด้านบนผมตั้งให้ field ชื่อ Code ของทรงรี เป็น primary key และไม่ซ้ำกัน ต่อไปเราจะ import หรือปั๊มข้อมูลเข้า table ซึ่งยังว่างๆในตอนนี้
การปั๊มข้อมูลเข้า Table
- ที่เมนูหลักคลิกที่ Data > Import data… จะเห็น dialog เป็นดังรูปด้านล่าง

- จากรูปด้านบน จุดที่ 1 คลิกที่ Open File เลือกไฟล์ ellipsoids.csv ที่เราเตรียมไว้ โปรแกรมจะถามว่าใช้อะไรเป็นเครื่องหมายคั่น ให้พิมพ์เครื่องหมายจุลภาค(comma) และถามต่อว่าไฟล์ ellipsoids.csv มีหัวไฟล์ (header column) ตอบ No ดูที่จุดที่ 2 เลือก Target Table เลือกตาราง Ellipsoids จุดที่ 3 ทำการ map column ของไฟล์ให้เข้ากับ field ของฐานข้อมูลดังรูปด้านบน จุดที่ 4 คลิกที่ Import Data เพื่อนำข้อมูลเข้าได้เลย
- ตรวจสอบข้อมูลได้ ดูรูปด้านล่างประกอบ

- จากรูปด้านบนตรงแท็ปด้านขวามือจะเห็น SQL Query คลิกเพื่อลองทดสอบการ query พิมพ์ sql ดังรูปด้านล่าง

- แล้วคลิกที่เมนูหลัก Query > Execute with Result คลิกไปที่แท็ป Result เพื่อดูผลลัพธ์ดังรูปด้านล่าง

การใช้เครื่องมือ Adimin ที่เป็น command line
- สำหรับความรู้สึกผมแล้วการใช้ command line ในบางครั้งกลับสะดวกกว่าโปรแกรมที่ใช้ GUI เสียอีก ตัวอย่างก็หนีไม่พ้นใน linux การใช้ command line เป็นเรื่องสะดวกมากๆ เช่นคำสั่ง ls, dmesg, df, env, lspci, lsmod อะไรประมาณนี้ การใช้เครื่องมือ admin ที่แนะนำให้ดาวน์โหลดไปตอนต้นคือ sqlite3.exe ผมจะแนะนำการใช้คร่าวๆ ผม copy ไปไว้ที่โฟลเดอร์ c:\sqlite3 ถ้าใช้ windows กดคีย์บอร์ด ALT + R พิมพ์คำสั่ง cmd กด Enter แล้วเรียกใช้คำสั่งดังรูปด้านล่างเพื่อเรียกโปรแกรม
ผมเปิดฐานข้อมูลด้วยคำสั่ง sqlite3 e:\sourcecodes\lazarus\sqliteproj1\datums.s3db ถ้าสงสัยหรือจำคำสั่งไม่ได้ก็ให้พิมพ์ .help เข้าไป- ผมใช้คำสั่ง .databases เพื่อดูว่า database อะไรที่เราใช้งานอยู่ ตามด้วย .header ON เพื่อให้แสดงคอลัมภ์ชื่อฟิลด์ และ .show เพื่อแสดงพารามิเตอร์ แล้วลอง query ดูด้วย syntax “select” ดังรูปด้านล่าง
จากรูปด้านบนจะเห็นผลลัพธ์ของ sql ด้วย syntax “select” ที่จริงยังมีคำสั่งอีกมากที่ทำได้ในสภาวะ command line แต่ตอนนี้ขอพอแค่นี้ก่อน ตอนหน้าจะลงลึกไปอีกนิดว่าจะนำ SQLite มาใช้กับ Lazarus อย่างไร
การเขียนโปรแกรมเพื่อคำนวณระยะทางและอะซิมัท (Distance/Azimuth) บน Ellipsoid ด้วย Lazarus (ตอนที่ 1)
- ตอนก่อนหน้านี้ ผมเขียนโปรแกรมแปลงพิกัดระหว่าง UTM และ Geographic (Lat/Long) และและถ้าไม่เขียนการหาระยะทางและอะซิมัท (เมื่อกำหนดจุด Latitude, Longitude ให้สองจุด) ก็ดูจะขาดอะไรไปอย่าง
Model ที่ใช้ในการคำนวณ
- สัณฐานหรือรูปทรงที่ใช้แทนโลก ใช้กันอยู่ 2 แบบ คือ ทรงกลม(Spherical)และทรงรี(Ellipsoid) แต่ทรงรีจะใกล้เคียงกับกับโลกเรามากกว่า ดังนั้นการหาระยะทางและทิศทาง(อะซิมัท) บนทรงรีจะให้ความละเอียดถูกต้องมากกว่า

รูปทรงที่ใช้แทนสัณฐานของโลก (ภาพอ้างอิงจาก http://www.codeguru.com/cpp/cpp/algorithms/article.php/c5115/)
- การคำนวณระยะทางและอะซิมัทบนทรงกลมนั้นง่ายกว่าการคำนวณบนทรงรี การคำนวณระยะทางบนทรงกลมเช่นต้องการทราบระยะทางจากจุด A ไปจุด B เราจะใช้ plane คือแผ่นเรียบตัดผ่านจุด A และจุด B และที่สำคัญคือต้องผ่านจุดศูนย์กลางทรงกลมด้วย เส้นที่เกิดจากแผ่น plane มาตัดกับทรงกลมจะเรียกว่า Great Circle ระยะที่สั้นที่สุดบนผิวทรงกลมก็คือระยะที่ไปตาม Great circle นี่เอง

Great circle อ้างอิงจาก http://www.codeguru.com/cpp/cpp/algorithms/article.php/c5115/
- ส่วนทรงรีที่สัณฐานใกล้เคียงกับโลกมากกว่า จะยุบลงที่ขั้วโลก โป่งออกที่เส้นศูนย์สูตร เราจะหาระยะทางและอะซิมัทโดยอิงสัณฐานของทรงรีโดยใช้สูตรอันลือชื่อของ Thaddeus Vincenty
สูตรที่ใช้อ้างอิงในการคำนวณ (Vincenty’s formulae)
- Thaddeus Vincenty เกิดที่โปแลนด์ อพยพเข้าสหรัฐอเมริกาเมื่อสงครามโลกครั้งที่สอง ทำงานให้กับ U.S. Air Force และ National Geodetic Survey ตามลำดับ ที่เป็นที่รู้จักกันมากที่สุดก็คือ Vincenty’s Formulae นี้เอง เขาเผยแพร่สูตรนี้เมื่อปี 1975 ซึ่งให้ความละเอียดสูงมากจนถึงครึ่งมิลลิเมตร
- สำหรับสูตรของคุณ Thaddeus Vincenty ถ้าเห็นสูตรยาวๆแล้วสารอะดรีนาลีนหลั่งออกมาก็ ไปดาวน์โหลดได้ที่ http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
- สำหรับสูตรที่จะนำเสนอในที่นี้ผมแปลมาจาก Javascript ดูได้ที่ http://www.movable-type.co.uk/scripts/latlong-vincenty.html สำหรับสูตรของ Vincenty จะมีการอินทิเกรต แต่ในทางโปรแกรมมิ่งจะใช้เทคนิค iteration แทนทำให้สูตรที่จะโปรแกรมดูกระชับมาก
- สำหรับสูตรที่นำสรุปจาก Vincenty’s formulae คำนวณหาระยะทางและอะซิมัทจากจุด 2 จุด แสดงให้เห็นดังข้างล่าง
| a, b = major & minor semiaxes of the ellipsoid | ||
| f = flattening (a−b)/a | ||
| φ1, φ2 = geodetic latitude | ||
| L = difference in longitude | ||
| U1 = atan((1−f).tanφ1) (U is ‘reduced latitude’) | ||
| U2 = atan((1−f).tanφ2) | ||
| λ = L (first approximation) | ||
| iterate until change in λ is negligible (e.g. 10-12 ≈ 0.06mm) { | ||
| sinσ = √[ (cosU2.sinλ)² + (cosU1.sinU2 − sinU1.cosU2.cosλ)² ] | (14) | |
| cosσ = sinU1.sinU2 + cosU1.cosU2.cosλ | (15) | |
| σ = atan2(sinσ, cosσ) | (16) | |
| sinα = cosU1.cosU2.sinλ / sinσ | (17) | |
| cos²α = 1 − sin²α (trig identity; §6) | ||
| cos2σm = cosσ − 2.sinU1.sinU2/cos²α | (18) | |
| C = f/16.cos²α.[4+f.(4−3.cos²α)] | (10) | |
| λ′ = L + (1−C).f.sinα.{σ+C.sinσ.[cos2σm+C.cosσ.(−1+2.cos²2σm)]} | (11) | |
| } | ||
| u² = cos²α.(a²−b²)/b² | ||
| A = 1+u²/16384.{4096+u².[−768+u².(320−175.u²)]} | (3) | |
| B = u²/1024.{256+u².[−128+u².(74−47.u²)]} | (4) | |
| Δσ = B.sinσ.{cos2σm+B/4.[cosσ.(−1+2.cos²2σm) − B/6.cos2σm.(−3+4.sin²σ).(−3+4.cos²2σm)]} | (6) | |
| s = b.A.(σ−Δσ) | (19) | |
| α1 = atan2(cosU2.sinλ, cosU1.sinU2 − sinU1.cosU2.cosλ) | (20) | |
| α2 = atan2(cosU1.sinλ, −sinU1.cosU2 + cosU1.sinU2.cosλ) | (21) | |
where :
-
- s is the distance (in the same units as a & b)
- α1 is the initial bearing, or forward azimuth
- α2 is the final bearing (in direction p1→p2)
สิ่งที่ต้องเตรียมก่อนจะโปรแกรม
- ยูนิต GeoEllipsoids (geoellipsoids.pas) เก็บสัณฐานทรงรีที่ใช้กันทั่วโลก ที่ผมเขียนไว้เรื่อง การเขียนโปรแกรมคำนวณการแปลงพิกัดระหว่าง UTM และ Geographic (ตอนที่ 2)
เริ่มต้นโปรแกรม
- ผมจะสร้างยูนิตที่ทำหน้าที่คำนวณชื่อ GeodesicCompute (อยู่ในไฟล์ geodesiccompute.pas) โค๊ดดังที่กล่าวไปแล้วจาก Javascript แต่ดัดแปลงให้เป็นโค๊ด OOP ในยูนิตนี้มี 2 class คือ TGeodesicInverse ทำหน้าที่คำนวณระยะทางและอะซิมัทเมื่อป้อนค่าพิกัดเป็น Latitude,Longitude 2 จุด เรียกการคำนวณนี้ว่า Inverse
- และอีกยูนิตหนึ่งคือ TGeodesicDirect(อยู่ในไฟล์ geodesiccompute.pas) ทำหน้าที่คำนวณหาค่าพิกัดจุดที่สอง (Lat/Long) เมื่อกำหนดค่าพิกัดจุดที่หนึ่งให้และ กำหนดอะซิมัทและระยะทาง เรียกการคำนวณนี้สั้นๆว่า Direct
GeodesicCompute Unit
- โค๊ดที่ผมเขียนดัดแปลงมาจาก Javascript เป็นไปดัง source code ด้านล่าง
unit GeodesicCompute;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, GeoEllipsoids, Math;
type
TZoneHemisphere = (hemEast, hemWest, hemNorth, hemSouth);
TCoordinate = record
X : double;
Y : double;
end;
//
// Vincenty Inverse Solution of Geodesics on the Ellipsoid.
// Calculate geodesic distance (in m) between two points
// specified by latitude/longitude (in numeric degrees) using
// Vincenty inverse formula for ellipsoids.
//
TGeodesicInverse = class
private
FEllipsoid : TEllipsoid;
FGeoCoor1 : TCoordinate;
FGeoCoor2 : TCoordinate;
FEllDist, FInitAzi, FFinalAzi : double;
procedure SetGeoCoordinate1 (const geocoor1 : TCoordinate);
procedure SetGeoCoordinate2 (const geocoor2 : TCoordinate);//input
procedure SetEllipsoid(const Ellipsoid : TEllipsoid);//input
function GeodesicInverse(lat1, lon1, lat2, lon2 : double) : double;
public
//input
property GeoCoordinate1 : TCoordinate write SetGeoCoordinate1;
property GeoCoordinate2 : TCoordinate write SetGeoCoordinate2;
//output
property Distance : double read FEllDist;
property InitialAzimuth : double read FInitAzi;
property FinalAzimuth : double read FFinalAzi;
//input
property Ellipsoid : TEllipsoid write SetEllipsoid;
//main computation
procedure Compute;
constructor Create;
destructor Destroy; override;
end;
// Calculate destination point given start point lat/long
// (numeric degrees), azimuth or bearing (numeric degrees)
// & distance (in m).
//
// from: Vincenty direct formula - T Vincenty,
// "Direct and Inverse Solutions of Geodesics on the
// Ellipsoid with application of nested equations",
// Survey Review, vol XXII no 176, 1975
// http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
TGeodesicDirect = class
private
FEllipsoid : TEllipsoid;
FGeoCoor1 : TCoordinate;
FGeoCoor2 : TCoordinate;
FAzimuth : double;
FDistance : double;
procedure SetGeoCoordinate1 (const geocoor1 : TCoordinate);
procedure SetEllipsoid(const Ellipsoid : TEllipsoid);
procedure SetAzimuth(const azi : double);
procedure SetDistance(const dist : double);
function GeodesicDirect(lat, lon, azimuth, distance : double) : TCoordinate;
public
//input
property Ellipsoid : TEllipsoid read FEllipsoid write SetEllipsoid;
property GeoCoordinate1 : TCoordinate write SetGeoCoordinate1;
property Azimuth : double write SetAzimuth;
property Distance : double write SetDistance;
//Output
property GeoCoordinate2 : TCoordinate read FGeoCoor2;
//main computation
procedure Compute;
constructor Create;
destructor Destroy; override;
end;
implementation
// TGeodesicInverse
procedure TGeodesicInverse.SetGeoCoordinate1 (const geocoor1 : TCoordinate);
begin
FGeoCoor1 := geocoor1;
end;
procedure TGeodesicInverse.SetGeoCoordinate2 (const geocoor2 : TCoordinate);
begin
FGeoCoor2 := geocoor2;
end;
procedure TGeodesicInverse.SetEllipsoid(const Ellipsoid : TEllipsoid);
begin
FEllipsoid.EllipsoidName := Ellipsoid.EllipsoidName;
FEllipsoid.MajorAxis := Ellipsoid.MajorAxis;
FEllipsoid.InvFlattening := Ellipsoid.InvFlattening;
end;
procedure TGeodesicInverse.Compute;
begin
FEllDist := GeodesicInverse(FGeoCoor1.Y, FGeoCoor1.X, FGeoCoor2.Y, FGeoCoor2.X);
end;
function TGeodesicInverse.GeodesicInverse(lat1, lon1, lat2, lon2 : double) : double;
var
a, b, f , e : double;
dlat1, dlon1, dlat2, dlon2 : double;
L, U1, U2 : double;
sinU1, cosU1, sinU2, cosU2 : double;
lambda, lambdaP, sinlambda, coslambda, sinsigma : double;
cossigma, sinalpha, sigma, cosSqalpha, cos2sigmaM : double;
cossigmaM, C, uSq, AA, BB, deltaSigma : double;
az1, az2 : double;
iterLimit : integer;
diff : double;
begin
dlat1 := PI/180 * lat1;
dlon1 := PI/180 * lon1;
dlat2 := PI/180 * lat2;
dlon2 := PI/180 * lon2;
a := FEllipsoid.MajorAxis;
f := 1/FEllipsoid.InvFlattening;
b := a*(1 - f);
L := dlon2-dlon1;
U1 := arctan((1-f) * tan(dlat1));
U2 := arctan((1-f) * tan(dlat2));
sinU1 := sin(U1);
cosU1 := cos(U1);
sinU2 := sin(U2);
cosU2 := cos(U2);
lambda := L;
iterLimit := 100;
repeat
sinlambda := sin(lambda);
coslambda := cos(lambda);
sinsigma := sqrt((cosU2*sinLambda) * (cosU2*sinlambda) +
(cosU1*sinU2-sinU1*cosU2*coslambda) *
(cosU1*sinU2-sinU1*cosU2*coslambda));
if (sinsigma = 0) then
begin
result := 0; // co-incident points
exit;
end;
cossigma := sinU1*sinU2 + cosU1*cosU2*coslambda;
sigma := arctan2(sinsigma, cossigma);
sinalpha := cosU1 * cosU2 * sinlambda / sinsigma;
cosSqAlpha := 1 - sinAlpha*sinAlpha;
cos2SigmaM := cosSigma - 2*sinU1*sinU2/cosSqAlpha;
if (IsNaN(cos2sigmaM)) then cosSigmaM := 0; // equatorial line: cosSqAlpha=0 (6)
C := f/16*cosSqalpha*(4+f*(4-3*cosSqalpha));
lambdaP := lambda;
lambda := L + (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*
(-1+2*cos2SigmaM*cos2SigmaM)));
dec(iterLimit);
diff := abs(lambda-lambdaP);
until ((diff < 1e-12) and (iterLimit > 0));
if (iterLimit = 0) then
begin
result := NaN; // formula failed to converge
exit;
end;
uSq := cosSqAlpha * (a*a - b*b) / (b*b);
AA := 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
BB := uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
deltaSigma := BB*sinsigma*(cos2sigmaM+BB/4*
(cossigma*(-1+2*cos2sigmaM*cos2sigmaM)-
BB/6*cos2sigmaM*(-3+4*sinsigma*sinsigma)*
(-3+4*cos2sigmaM*cos2sigmaM)));
result := b*AA*(sigma-deltaSigma); //return Ellipse Distance.
//initial azimuth.
az1 := arctan((cosU2*sinlambda)/(cosU1*sinU2 - sinU1*cosU2*coslambda));
//final azimuth.
az2 := arctan((cosU1*sinlambda)/(-sinU1*cosU2 + cosU1*sinU2*coslambda));
FInitAzi := az1 * 180/PI;
if (FInitAzi < 0) then FInitAzi := 360 + FInitAzi;
FFinalAzi := az2 * 180/PI;
if (FFinalAzi < 0) then FFinalAzi := 360 + FFinalAzi;
end;
constructor TGeodesicInverse.Create;
begin
inherited create;
FEllipsoid := TEllipsoid.Create;
end;
destructor TGeodesicInverse.Destroy;
begin
FEllipsoid.Free;
inherited Destroy;
end;
// TGeodesicDirect
procedure TGeodesicDirect.SetGeoCoordinate1 (const geocoor1 : TCoordinate);
begin
FGeoCoor1 := geocoor1;
end;
procedure TGeodesicDirect.SetAzimuth (const azi : double);
begin
FAzimuth := azi;
end;
procedure TGeodesicDirect.SetDistance (const dist : double);
begin
FDistance := dist;
end;
procedure TGeodesicDirect.SetEllipsoid(const Ellipsoid : TEllipsoid);
begin
FEllipsoid.EllipsoidName := Ellipsoid.EllipsoidName;
FEllipsoid.MajorAxis := Ellipsoid.MajorAxis;
FEllipsoid.InvFlattening := Ellipsoid.InvFlattening;
end;
procedure TGeodesicDirect.Compute;
begin
FGeoCoor2 := GeodesicDirect(FGeoCoor1.Y, FGeoCoor1.X, FAzimuth, FDistance);
end;
function TGeodesicDirect.GeodesicDirect(lat, lon, azimuth, distance : double) : TCoordinate;
var
a, b, f , e, s : double;
dlat1, dlon1, dlat2, dlon2 : double;
sinU1, alpha1, sinAlpha, sinAlpha1, cosAlpha1, tanU1, cosU1, sigma1 : double;
cos2SigmaM, sinSigma, cosSigma, cosSqAlpha, uSq, deltaSigma : double;
AA, BB, sigma, sigmaP, tmp, lambda, C, L, lat2, revAz : double;
begin
dlat1 := PI/180 * lat;
dlon1 := PI/180 * lon;
a := FEllipsoid.MajorAxis;
f := 1/FEllipsoid.InvFlattening;
b := a*(1 - f);
s := distance;
alpha1 := azimuth * PI/180.0;
sinAlpha1 := sin(alpha1);
cosAlpha1 := cos(alpha1);
tanU1 := (1-f) * tan(dlat1);
cosU1 := 1 / sqrt((1 + tanU1*tanU1));
sinU1 := tanU1*cosU1;
sigma1 := arctan2(tanU1, cosAlpha1);
sinAlpha := cosU1 * sinAlpha1;
cosSqAlpha := 1 - sinAlpha*sinAlpha;
uSq := cosSqAlpha * (a*a - b*b) / (b*b);
AA := 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
BB := uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
sigma := s / (b*AA);
sigmaP := 2*PI;
while (abs(sigma-sigmaP) > 1e-12) do
begin
cos2SigmaM := cos(2*sigma1 + sigma);
sinSigma := sin(sigma);
cosSigma := cos(sigma);
deltaSigma := BB*sinSigma*(cos2SigmaM+BB/4*
(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
BB/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*
(-3+4*cos2SigmaM*cos2SigmaM)));
sigmaP := sigma;
sigma := s / (b*AA) + deltaSigma;
end;
tmp := sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
dlat2 := arctan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
(1-f)*sqrt(sinAlpha*sinAlpha + tmp*tmp));
lambda := arctan2(sinSigma*sinAlpha1,
cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
C := f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
L := lambda - (1-C) * f * sinAlpha *
(sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*
(-1+2*cos2SigmaM*cos2SigmaM)));
revAz := arctan2(sinAlpha, -tmp); // final bearing
result.X := lon + L * 180/PI;
result.Y := dlat2 * 180/PI;
end;
constructor TGeodesicDirect.Create;
begin
inherited create;
FEllipsoid := TEllipsoid.Create;
end;
destructor TGeodesicDirect.Destroy;
begin
FEllipsoid.Free;
inherited Destroy;
end;
end.
- สำหรับ source code ที่แสดงดังข้างต้น เป็น class ง่ายๆ เช่น TGeodesicInverse จะมี property สำหรับ input อยู่ 3 ตัวคือ ค่ารูปทรงสัณฐาน Ellipsoid, GeoCoordinate1 และ GeoCoordinate2 เมื่อ input ค่า lat,long 2 ค่าพิกัดให้เสร็จ แล้วก็เรียกเมธอด Compute เพื่อทำการคำนวณ จากนั้นเรียกค่าจาก 2 property คือ Azimuth และ Distance ซึ่งจะเป็นผลลัพธ์การคำนวณ
- ส่วนคลาส TGeodesicDirect ก็เหมือนๆกัน มี property 4 ตัว คือ Ellipsoid, GeoCoordinate1, Azimuth และ Distance เมื่อรับค่ามาก็จะเรียกเมธอด Compute และให้ผลลัพธ์เป็นค่าพิกัด (lat,long) ของจุดที่ต้องการหา
ปัญหาความเข้ากันของ Widget ในแต่ละ Platform
- รูปที่จะแสดงให้ดูเป็นปัญหาของ Widget ผมเพิ่งจะประสบความสำเร็จติดตั้ง Widget ของ Qt บน PCLinuxOS เมื่อรันโปรแกรมด้วยชุด source code เดียวกันเปรียบเทียบกับ Win32 บนวินโดส์ และ Gtk2 บน Ubuntu พบว่า ขนาดของ Lable, EditBox, ComboBox, Groupbox ทั้งหลายบน Gtk2 มีขนาดใหญ่กว่าเพื่อน แต่พบว่าขนาดของ object พวกนี้บน Win32 และ Qt มีขนาดใกล้เคียงกัน เพื่อให้เข้ากับ Gtk2 จึงต้องมาขยับ เช่นตัว Label กับ EditBox จะต้องอยู่ห่างกันพอสมควร ไม่งั้นตัว Label จะล้ำเข้าไปหา EditBox แต่ความสวยงามดูๆ QT จะสวยเนียนกว่าเพื่อน รองลงมาเป็น Gtk2 กับ Win32 ดูรูปผลลัพธ์ของโปรแกรมที่ผมเขียนในตอนนี้

โปรแกรมคำนวณหาระยะทางและอะซิมัทบน Ellipsoid รันด้วย Qt (PCLinuxOS)

โปรแกรมคำนวณหาระยะทางและอะซิมัทบน Ellipsoid รันด้วย Gtk2 (Ubuntu)

โปรแกรมคำนวณหาระยะทางและอะซิมัทบน Ellipsoid รันด้วย Windows
- ทิ้งท้ายกันก่อนในตอนนี้ ติดตามกันตอนหน้า
การสร้าง (Create/Generate) DEM จาก DTM (3D Points) ด้วย Global Mapper
- ตัว Global Mapper สำหรับความรู้สึกผมนั้นดูเรียบง่ายและทรงพลัง การสร้าง DEM (Digital Elevation Model) จากโปรแกรมดังๆอย่าง Erdas Imagine หรือ ArcGIS ยังดูยากไปนิด มาดูกันว่า Global Mapper นั้นเรียบง่ายเพียงใด
Global Mapper V.10
- ผมมีเหตูต้องศึกษาเรื่องน้ำที่แม่น้ำตะนาวศรี(Tanintharyi River) ชื่อน่าจะคุ้นคนไทยเรา หลักสูตรวิชาภูมิศาสตร์จะมีเทือกเขาตะนาวศรีคั่นไทยกับพม่าอยู่ และก็แม่น้ำที่ไหลเคียงข้างกับเทือกเขาตะนาวศรีด้านพม่าก็คือแม่น้ำตะนาวศรีนี้เอง คนพม่าเรียกแม่น้ำนี้ว่า ตาเน็นตายี่
- ผมใช้ข้อมูลความสูง ซึ่งมีอยู่ 2 sources ต้องนำมาผสมกัน ข้อมูลแรกเป็น DTM (Digital Terrain Model) ของพม่า โดยที่ DTM ได้ข้อมูลมาเป็นจุดซึ่งวัดมาจากภาพถ่าย Stereo (พม่าจัดทำแผนที่ สเกล 1:50000 ใหม่ทั้งประเทศด้วยกระบวนการภาพถ่ายทางอากาศเมื่อปี 2005 เป็นแผนที่ภาพถ่ายทางอากาศ (Orthophoto map) และแผนที่ลายเส้น) ข้อมูลที่สองเป็น DEM ของ SRTM DEM ที่ผมเคยเขียนวิธีการ download ไปแล้ว

3D points as DTM (Digital Terrain Model)
- จากรูปด้านบนจะเห็นจุดที่เรียงเป็นกริดนั้น import มาจาก SRTM DEM ส่วนจุดที่กระจายไม่เป็นระเบียบเป็น 3D points ที่ได้จากก DTM ของพม่า
- ที่เมนูหลักคลิกที่ Tools > Control Center… หรือใช้ shortcut ก็ได้ด้วยการกด Alt + C ผมมี layer ที่เก็บ points รวมไว้แล้วเป็น layer เดียว ที่เลเยอร์นี้คลิกขวาเลือก Create Elevation Grid form 3D Vector Data…

Create elevation grid.
- จะมีไดอะล็อก ให้ตั้งเงื่อนไขของ Elevation Grid ก่อน

ตั้งค่าเพื่อสร้าง Elevation grid
- จากรูปด้านบนตั้งชื่อที่ Description ผมตั้งเป็น Tanintaryi_DEM ระยะ spacing ของกริดผมเลือกเป็น 10 เมตรทั้ง x และ y จากนั้นตัวอื่นทิ้งไว้ตามค่าปริยายคลิกที่ OK เพื่อเริ่มสร้าง elevation grid โปรแกรม Global Mapper จะเริ่มสร้าง TIN (Triangulation Irregular Network) ใช้เวลาประมาณ 3-4 นาทีต่อจำนวนจุดประมาณ 1.4 ล้านจุดก็ถือว่าเร็ว ต่อจากนั้นก็ฟอร์มเป็น Elevation Grid ซึ่งพร้อมจะสร้างเป็น DEM ต่อไป

Generated DEM
- จากรูปด้านบนเมื่อกด ALT+C ตัว Control Center จะพ๊อพอัพออกมา ติ๊กที่ layer 3d points ออก ให้เห็น layer ใหม่คือ Tanintaryi_Dem อย่างเดียวจะเห็นรูป DEM สวยงามดังรูปข้างบน
การ Export เป็น DEM
- ที่เมนูหลัก คลิกที่ File > Export Raster and Elevation Data > Export GeoTiff… เราจะ Export เป็น GeoTiff DEM

Export to DEM
- จะเห็นไดอะล็อกถาม options ของฟอร์แม็ต GeoTiff ตั้งค่าดังรูปด้านล่าง

ตั้งค่า GeoTiff
- จากรูปด้านบนเมื่อคลิก OK โปรแกรมจะให้ป้อนชื่อไฟล์ GeoTiff เมื่อป้อนชื่อไฟล์ รอประมาณ 3 นาทีก็จะเสร็จ ทดสอบดูว่ามีอะไรผิดพลาดหรือไม่ลองไปเปิดด้วยโปรแกรมอื่นผมใช้ Viewer ของ Erdas Imagine ก็ OK

Viewer by Erdas Imagine





เมนูแก้ไข Coordinate Systems






