Archive

Archive for พฤศจิกายน, 2009

ปฐมบท “GDAL/OGR “ สุดยอดไลบรารีสำหรับการพัฒนาโปรแกรม GIS

200px-GDALLogoColor.svg

  • ตอนก่อนๆผมเคยแนะนำ GDAL (Geospatial Data Abstraction Library) เป็น Library แบบ opensource ใช้จัดการอ่านและเขียนภาพ (Raster) ที่อ้างอิงกับระบบพิกัดภูมิศาสตร์ ไลบรารีพัฒนาด้วยภาษา glibc/glibc++
  • ส่วน OGR ก็เช่นเดียวกันเป็นส่วนหนึ่งของ GDAL แต่ใช้กับจัดการกับไฟล์ Vector
  • GDAL/OGR เดิมที่ก่อนหน้าเวอร์ชั่น 1.3.2 พัฒนาโดย Frank Warmerdam ภายหลังโอนย้ายภาระกิจไปยังสมาคม GDAL/OGR Project Management ซึ่งอยู่ภายใต้ Open Source Geospatial Foundation ซึ่งมั่นใจได้ว่าโครงการนี้จะดำเนินการพัฒนาต่อไปเรื่อยๆ
  • ในฐานะนักพัฒนาโปรแกรมอื่นๆเช่น Perl, Python, Ruby, C#, Java สะดวกมากสามารถนำไลบรารีนี้ไปใช้ได้เลย โค๊ดของ glibc++ สามารถแปลหรือ การทำ Wrapper ไปหาภาษาเหล่านี้ได้ด้วย SWIG (Simplified Wrapper and Interface Generator) แค่ดาวน์โหลด sourcecode ของ GDAL/OGR ไปก็ใช้งานได้เลย
  • ส่วน Visual Basic 6 (แต่ยังไม่มี VB.Net) ก็มีคนทำ Wrapper ไปใช้งานซึ่งไม่ได้ทำผ่าน SWIG เพราะ SWIG ไม่ได้รองรับ VB เช่นเดียวกันกับ Delphi หรือ Lazarus ถ้าดาวน์โหลด sourcecode ก็จะได้ wrapper สำหรับ VB6 ไปใช้ การดาวน์โหลดถ้าผ่าน subversion ก็สะดวกเพราะจะได้เวอร์ชั่นที่ update ไปใช้งานได้ตลอด และตัว sourcecode ตัวนี้สามารถคอมไพล์ด้วย MS C++ เพื่อให้ได้ไลบรารี *.dll ไปใช้งาน แต่สำหรับผมแล้วไม่ได้ใช้ MS C++ จึงสะดวกที่จะดาวน์โหลดไลบรารีที่ผ่านการคอมไพล์เป็น .dll แล้วจาก website ไปใช้งาน
  • ในเบื้องต้นผมใช้ตัว wrapper ของ VB6 เป็นแนวทางของการทำ wrapper ด้วย Lazarus ภายหลังเพิ่มฟังก์ชั่นของ GDAL เข้าไปมากกว่าของ VB6 หลักการทำ wrapper คือเขียน class ขึ้นมาห่อฟังก์ชั่นที่เรียกใช้งานไลบรารีของ GDAL ทำให้สะดวกในการเรียกใช้งานเมื่อนำไปใช้พัฒนาโปรแกรม
  • GDAL ออกเสียงเรียกเป็น “จี ดอล” หรือ “กู ดอล” เนื่องจาก OGR เป็นส่วนหนึ่งของ GDAL ดังนั้นเมื่อเรียก GDAL ให้หมายถึง GDAL/OGR

การสนับสนุนฟอร์แมต

  • ผมจะยังไม่พูดถึง OGR ในตอนนี้ขอกล่าวถึงเฉพาะ GDAL (Raster) ก่อน ซึ่ง GDAL เองสนับสนุนฟอร์แมตของ Raster file มากถึง 75 ฟอร์แม็ต ซึ่งครอบคลุมฟอร์แม็ตที่สำคัญไว้ครบถ้วน เช่น GeoTIFF, Erdas Imagine, SDTS, ECW, MrSID, JPEG2000, DTED, NITF
  • รายละเอียดเต็มๆดูได้ที่นี่ http://www.gdal.org/formats_list.html

Download & Install

  • ผมจะขอพูดถึง platform เฉพาะของวินโดส์ก่อน ส่วนใน Linux ผมพยายามใช้ library (libgdal1.6.0.so) ของ GDAL แต่ยังไม่สำเร็จตอนทำ linking เกิด error ยังหาสาเหตุไม่พบ จึงขอข้ามไปก่อน ผมจะดาวน์โหลดที่เป็นไบนารีไฟล์ของ GDAL ไปก่อน ซึ่งที่จะดาวน์โหลดต่อไปนี้ จะรวม utility หลายๆตัวซึ่งเป็น execute file ไปใช้งานได้ด้วย แต่เป็น command line นะครับ ซึ่งผมใช้บ้างแต่ไม่บ่อย
  • เริ่มกันเลยไปดาวน์โหลดได้ที่ http://download.osgeo.org/gdal/win32/1.6/gdalwin32exe160.zip แล้วทำการติดตั้ง โปรแกรมติดตั้งจะเลือกติดตั้งที่รากของ Drive C ที่โฟลเดอร์ c:\gdalwin32-1.6 ตอนนี้เป็นเวอร์ชั่น 1.6 ดูรูปด้านล่างจะเห็นในโฟลเดอร์ย่อนคือ bin จะมี execute file (*.exe) ซึ่งเป็น command line และที่เหลือเป็นไลบรารี Dynamic Link Library (*.dll) ที่ผมจะทำ wrapper ด้วย Lazarus เพื่อดึงไลบรารีมาใช้งาน แต่ที่เราเรียกใช้จริงๆคือ gdal16.dll ซึ่งในไฟล์นี้จะรวม OGR (vertor) มาเรียบร้อยแล้ว

gdalbinfolder

การตั้ง Path ให้ Library

  • ถ้าต้องการใช้ execute file ซึ่งเป็น command line ให้ใช้งานได้สะดวกจะต้องตั้ง library path ให้วินโดส์ได้รับรู้ก่อน ซึ่งเป็นวิธีค่อนข้างโบราณตั้งแต่ DOS ไม่เป็นไร ไม่ใช่เรื่องลำบากนัก ที่ My Computer ของวินโดส์ คลิกขวาเลือก Properties คลิกไปที่แท็บ Advance แล้วคลิกต่อที่ Environment Variables ดูรูปด้านล่างประกอบ setpath01
  • จากนั้นคลิกไปที่บรรทัด Path แล้วคลิกที่ปุ่ม Edit

setpath02

  • พิมพ์ path ของไลบรารีของ GDAL เข้าไปดังรูป (อย่าลืมปิดท้ายด้วยเครื่องหมาย ;) เท่านี้ก็เรียบร้อย เวลาเราใช้ Lazarus ตอน compile & linking โปรแกรมที่เราพัฒนา Lazarus จะได้หาไฟล์ gdal16.dll มา link ได้ถูกต้อง

setpath03

Tools ที่ต้องการมาช่วยการ Export ไลบรารี

  • แต่ละฟังก์ชั่นที่เราจะดึงมาใช้เช่นดูโค๊ดของ c++ มี export name เป็นอย่างไร

CPLErr GDALSetMetadataItem (GDALMajorObjectH hObject, const char *pszName,
                            const char *pszValue, const char *pszDomain)

  • ใน website ของ GDAL ไม่ได้แสดงไว้ให้เราดู ต้องการ tools ซึ่งผมจะแนะนำต่อไป มาดูโค๊ดทีของ Lazarus ที่เรียกใช้ฟังก์ชั่นนี้ จะเห็นฟังก์ชั่นมี export name เป็น ‘_GDALSetMetadataItem@16′ ซึ่งถ้าชื่อตรงนี้ไม่ถูก Lazarus ไม่สามารถ link ได้

function GDALSetMetadataItem (const Handle : TGDALMajorObjectH;
                              const Name : PCHAR;
                              const Value : PCHAR;
                              const Domain : PCHAR
                              ) : longint; stdcall;
                              external External_Lib name '_GDALSetMetadataItem@16';

  • ดูโค๊ดด้านบนจะเห็น External_Lib เป็นตัวแปร string constant ผมให้เก็บ ‘gdal16.dll’ ถ้าฝั่ง Linux เปลี่ยนเป็น ‘libgdal1.6.0.so’  อะไรประมาณนี้

Download โปรแกรม Dllexp (Dll Export Viewer 1.36)

  • tool ตัวนี้มีขนาดเล็กมากๆ แค่ 44 KB เองแต่เก่งมาก สิ่งที่ต้องการมีครบหมด ไปดาวน์โหลดได้ที่ http://www.download3k.com/Install-DLL-Export-Viewer.html ได้ zip file มาก็ unzip ไปวางไว้ที่โฟลเดอร์ตรงไหนก็ได้แล้วทำ shortcut ก็เรียกใช้ได้เลย มาลองดู tool ตัวนี้ผมลองรันดังรูปด้านล่าง

dllexp01

  • จากรูปด้านบน เราคลิกไปที่ browse เพื่อเลือกไลบรารีดังรูป คลิกที่ OK เพื่อดูชื่อ export function

dllexp02

  • ที่ผมทำลูกศรชี้ไว้ด้านบนเป็น tools ที่เราใช้บ่อย ตัวซ้ายมือเป็น property ของฟังก์ชั่นที่เราเลือก ตัวขวามือเป็นเครื่องค้นหาชื่อฟังก์ชั่น ส่วนลูกศรด้านล่างเป็นฟังก์ชั่นที่ผมกล่าวไปแล้วคือ _GDALSetMetadataItem@16 ชื่อที่เราเห็นใน sourcecode คือ GDALSetMetadataItem ลองคลิก property ที่ toolbar ดู

dllexp03

  • ส่วน toolbar ค้นหาผมจะใช้ชื่อฟังก์ชั่นภาษาซี มาป้อนแล้วค้นหาเมื่อพบฟังก์ชั่นแล้วเราจะ copy ชื่อ function name นี้ไปวางที่ฟังก์ชั่น wrap ด้วย Lazarus หลัง external library name (‘gdal16.dll’)
  • ถ้าเราดูโค๊ดของ GDAL มีฟังก์ชั่นเป็นจำนวนมากไม่ได้ export แต่โปรแกรม Dllexp ช่วยเราได้ในตรงจุดนี้ ตัวอย่างเช่นฟังก์ชั่นสร้าง memory คือ CPLMalloc() ไปดูใน DllExp เห็น ไม่มีปัญหาใช้งานได้ แต่ฟังก์ชั่นตัวคืน momery คือ CPLFree() กลังไม่พบใน gdal16.dll ซึ่งแปลกมากให้สร้างแต่ไม่ export ฟังก์ชั่นคืน memory ให้ใช้

ปัญหาความแตกต่างของตัวแปรระหว่าง C/C++ และ Lazarus

  • ปัญหาการรับส่งตัวแปรในฟังก์ชั่นของ C++ จะเห็นว่าเต็มไปด้วย Pointer ซึ่งในภาษาซีการใช้ pointer เป็นเรื่องปกติ แต่ถ้าเป็น Lazarus จะยุ่งยากในการใช้ตัวแปรเพื่อให้ compatible กับ C++ แต่ผมดูแล้ว Lazarus คงเล็งเห็นปัญหาในจุดนี้เตรียม Type ของตัวแปรมาให้ค่อนข้างดี (ผมว่าดีกว่า Delphi ที่ผมใช้เมื่อก่อนเสียอีก) ดูตัวอย่างโค๊ดของภาษาซีด้านล่าง

    CPLErr GDALSetRasterCategoryNames(GDALRasterBandH hBand, char **papszNames)

  • ตัว CPLErr เป็น Type ข้อมูลแบบ enumerate ใน Lazarus ผม declare ให้เทียบเท่าเป็น

TCPLErr = (CE_None = 0, CE_Debug = 1,
           CE_Warning = 2, CE_Failure = 3,
           CE_Fatal = 4);

  • ส่วนในโค๊ดภาษาซี ตัวถัดไปคือ GDALRasterBandH เป็น pointer (เก็บ handle ของ object) ใน Lazarus เรา declare ได้ง่ายๆเป็น

TGDALMajorObjectH = POINTER;

  • โค๊ดถัดไปของฟังก์ชั่นภาษาซีตัวต่อไป คือ char **papszNames (Pointer to pointer of char) จะเทียบเท่ากับ Lazarus อย่างไร ใน VB6 ใช้ variant แทนแต่ Lazarus ใช้ variant ไม่ได้ แต่โชคดี Lazarus เตรียมตัวแปรแบบนี้มาให้พร้อมคือ PPCHAR ผมหลงไปตั้งนาน เมื่อมารู้ว่า Lazarus เตรียมมาให้แล้ว งานก็ดูง่ายขึ้นเป็นอันมาก ฟังก์ชั่นใน Lazarus ก็ declare ได้ดังนี้

function GDALSetRasterCategoryNames(hBand : TGDALRasterBandH;
                                    papszNames : PPCHAR
                                    ) : TCPLErr; stdcall;
                                   external External_Lib name '_GDALSetRasterCategoryNames@8';

Pointer to Array

  • ดูโค๊ดของภาษาซีตรง double *padfTransform ตัวแปร padfTransform จะเป็นตัวแปร pointer ชี้ไปที่ array ของตัวแปรแบบ double

    CPLErr GDALSetGeoTransform(GDALDatasetH hDS,double *padfTransform)

  • ใน Lazarus เปลี่ยนชื่อตัวแปรให้สอดคล้อง และ pointer to array  แทนด้วย PDOUBLE ซึ่ง Lazarus ก็เตรียมมาให้เช่นเดียวกันคือ PDOUBLE ฟังก์ชั่นของ Lazarus จึงเขียนได้ดังนี้

function GDALSetGeoTransform (const Handle : TGDALDatasetH;
                              Geotransform : PDOUBLE) : TCPLErr;
                              stdcall; external External_Lib name '_GDALSetGeoTransform@8';
 

Pointer to Function

  • ในฟังก์ชั่นไลบรารีของ GDAL จะมีตัวแปร pointer to function ด้วย ดูโค๊ดของภาษาซีด้านล่าง จะเห็นตัวแปร GDALProgressFunc pfnProgress

CPLErr GDALBuildOverviews(GDALDatasetH hDataset,
                          const char *pszResampling,
                          int  nOverviews,
                          int *panOverviewList,
                          int  nListBands,
                          int *panBandList,
                          GDALProgressFunc pfnProgress,
                          void *  pProgressData)

  • ใน Lazarus เรา declare  type และฟังก์ชั่นดังนี้

TGDALProgressFunc = function(dfComplete : double; const pszMessage : PCHAR;
function GDALBuildOverviews(hDataset : TGDALDatasetH;
                            const pszResampling : PCHAR;
                            nOverviews : longint;
                            panOverviewList : PINTEGER;
                            nListBands : longint;
                            panBandList : PINTEGER;
                            pfnProgress : TGDALProgressFunc;
                            pProgressData : POINTER
                           ) : TCPLErr; stdcall; external External_Lib name '_GDALBuildOverviews@32';

  • ส่วนตัวแปร void *pProgressData ใน Lazarus แทนด้วย pProgressData : POINTER

ตัวแปร 16 bit, 32 bit และ 64 bit

  • เนื่องจากฟอร์แม็ตไฟล์ข้อมูลจำพวก Raster จะหลากหลายมากบางฟอร์แม็ตข้อมูลแต่ละ pixel ใช้ Byte บ้าง ใช้ integer  16 bit แบบ signed และ unsigned บ้าง ดังนั้น type ตัวแปรที่จะอ่านเขียน pixel ต้องสอดคล้องกัน ดูโค๊ดของภาษาซี ที่ declare ชนิดข้อมูลของ pixel ใน web

enum  GDALDataType {GDT_Unknown = 0, GDT_Byte = 1, GDT_UInt16 = 2,
                    GDT_Int16 = 3, GDT_UInt32 = 4, GDT_Int32 = 5,
                    GDT_Float32 = 6, GDT_Float64 = 7, GDT_CInt16 = 8,
                    GDT_CInt32 = 9, GDT_CFloat32 = 10, GDT_CFloat64 = 11,
                    GDT_TypeCount = 12
                   }

  • ใน website อธิบายตัวแปรแบบ enumerate ชื่อ GDALDataType

enum GDALDataType

Pixel data types

Enumerator:

GDT_Unknown - Unknown or unspecified type

GDT_Byte - Eight bit unsigned integer

GDT_UInt16 - Sixteen bit unsigned integer

GDT_Int16 - Sixteen bit signed integer

GDT_UInt32 - Thirty two bit unsigned integer

GDT_Int32 - Thirty two bit signed integer

GDT_Float32 -  Thirty two bit floating point

GDT_Float64  - Sixty four bit floating point

GDT_CInt16  - Complex Int16

GDT_CInt32  - Complex Int32

GDT_CFloat32  - Complex Float32

GDT_CFloat64  - Complex Float64

  • ใน Lazarus เรา declare ดังนี้

type
  GInt32   = longint;
  GUInt32  = longword;
  GInt16   = shortInt;
  GUInt16  = word;
  GByte    = byte;
  GBool    = boolean;
  GFloat32 = single;
  GFloat64 = double;
  TGDALDataType = (GDT_Unknown = 0, GDT_Byte = 1, GDT_UInt16 = 2,
                   GDT_Int16 = 3, GDT_UInt32 = 4, GDT_Int32 = 5,
                   GDT_Float32 = 6, GDT_Float64 = 7, GDT_CInt16 = 8,
                   GDT_CInt32 = 9, GDT_CFloat32 = 10, GDT_CFloat64 = 11,
                   GDT_TypeCount = 12);

โครงสร้างของ Model ของ GDAL

  • โครงสร้าง Class ของไลบรารี GDAL เป็นไปดังรูปด้านล่างผมพยายาม wrapper เป็น class ให้สอดคล้องกับ model ของ GDAL เช่นเดียวกัน ต่อไปผมจะอธิบายแต่ละ class พอเป็นสังเขป

  • จากรูป model ด้านบน จะมี classs “GDALMajorObject” ซึ่งลักษณะเป็น abstract class หรือ class ต้นแบบ ส่วนใน Lazarus ผม declare เป็น TGDALMajorObject (อยู่ในยูนิตไฟล์ gdalcore.pas)
  • class “GDALDataset” จะเกี่ยวข้องกับ Raster band ทั้งหมดรวมทั้งข้อมูลบางอย่างของ raster file เช่นขนาดของรูปเป็น pixel และจัดเก็บ Metadata พร้อมทั้งระบบพิกัดภูมิศาสตร์ รวมทั้งการแปลงค่าพิกัดจากระบบหนึ่งไปอีกระบบหนึ่ง ใน Lazarus ผม declare เป็น TGDALDataset (อยู่ในยูนิตไฟล์ gdaldataset.pas)
  • class “GDALDriverManager” เกี่ยวข้องกับฟอร์แม็ตข้อมูล ทาง GDAL จะเรียกเป็น driver สามารถหาจำนวนฟอร์แม็ตที่สนับสนุน ค้นหา รวมทั้งสามารถ register ฟอร์แม็ตข้อมูลก็ได้ ใน Lazarus ผม declare เป็น TGDALDriverManager อยู่ในยูนิต gdaldrivermanager.pas)
  • class “GDALDriver” เกี่ยวข้องกับ Dataset แต่จะเป็นลักษณะการสร้าง เปลี่ยนชื่อ การ copy และลบไฟล์ข้อมูล ใน Lazarus คือ TGDALDriver (อยู่ในยูนิตไฟล์ gdaldriver.pas)
  • class “GDALRasterBand” เกี่ยวข้องกับ raster band เพียงแค่หนึ่งแบนด์เท่านั้น เช่นในไฟล์หนึ่งๆอาจจะมี 3 แบนด์ (แดง เขียว น้ำเงิน) สามารถอ่านและเขียนในระดับ pixel หรือ อ่านและเขียนเป็น block ได้ สามารถอ่าน Color table ได้ผ่าน class “GDALColorTable” ใน Lazarus คือ TGDALRasterBand (อยู่ในยูนิตไฟล์ gdalrasterband.pas) และ TGDALColorTable (อยู่ในยูนิตไฟล์ gdalcolortable.pas) ตามลำดับ
  • และต่อไปเป็นคลาสที่ไม่ได้อยู่ใน model แต่ก็ถูกใช้เมื่ออ้างอิงถึงระบบพิกัด ได้แก่ class “OGRSpatialReference” และ class “ogrcoordinatetransformation” ให้บริการเรื่องระบบพิกัดทั้งเส้นโครงแผนที่และพื้นหลักฐาน(Projections&Datums) ใน Lazarus เป็น class “TOGRSpatialReference” (ogrspatialreference.pas) และ class “TOGRCoordinateTransformation” (ogrcoordinatetransformation.pas)
  • และที่เพิ่มเติมสำหรับโค๊ดของ Lazarus คือยูนิตไฟล์ gdal.pas เก็บ constant และ data type และอีกไฟล์คือ gdalcore.pas สำหรับเก็บ export function จาก GDAL library

TOGRSpatialReference และ TOGRCoordinateTransformation งานยังไม่จบ

  • สำหรับสอง class ข้างต้นมีฟังก์ชั่นมากเกี่ยวกับเส้นโครงแผนที่และพื้นหลักฐาน การแปลงพิกัด ผมยัง port เข้าหา Lazarus ยังไม่เสร็จต้องใช้เวลาอีกมากแต่ก็มีฟังก์ชั่นที่สำคัญเอาไปใช้งานก่อน ความจริงที่ผมเขียนโปรแกรมเกี่ยวกับด้านการแปลงพิกัดตอนก่อน เราสามารถใช้ GDAL มาทำแทนได้เลยเพียงแต่ต้องศึกษาก่อนว่าอะไรเป็นอะไร จะลดงานลงไปได้มาก (สองคลาสนี้ในเบื้องต้นผู้พัฒนา GDAL ได้ port โค๊ดมาจากโครงการ Proj4 ที่จริงผู้พัฒนา Proj4 ก็คือ Frank Warmerdam คนๆเดียวกันที่พัฒนา GDAL) ยุคนี้ไม่ใช่ยุคแต่ก่อนที่โปรแกรมเมอร์ต้องโค๊ดโปรแกรมด้วยตัวเองทั้งหมด ถ้าพัฒนาโปรแกรมด้าน GIS จะได้มีเวลาและพุ่งโฟกัสไปที่จุดมุ่งหมาย ไม่ต้องห่วงเรื่องการอ่านไฟล์ raster & vector เพราะ GDAL ทำให้แล้ว ไม่ต้องห่วงเรื่องระบบพิกัดเพราะ GDAL ทำให้แล้วเช่นเดียวกัน ถ้าสามารถหา component เก่งๆที่ช่วยเรื่องระบบกราฟฟิคได้ก็ดีมาก จะลดงานไปอีกได้มาก
  • โชคดียุคนี้เป็นยุคของ opensource เป็นโลกของการแบ่งปันและร่วมกันพัฒนา จึงมีอะไรดีๆอีกมากในโลกของอินเทอร์เน็ต ถ้าหาให้เจอแค่นั้นเอง ที่ผมเขียน blog ส่วนหนึ่งเพราะต้องการแบ่งปันประสบการณ์ และก็มีความสุขครับ
  • ตอนหน้าจะมาดูเรื่องโปรแกรมมิ่งด้วย Lazarus ในเบื้องต้น ส่วน sourcecode ของ GDAL library ผมหาที่ฝากได้แล้ว ผู้อ่านสามารถจะ download ไปลองรันดูได้
Categories: GIS, Lazarus, Programming, Windows ป้ายกำกับ:, , , , , ,

GeoViewer และ ER Viewer สองสุดยอดเครื่องมือดูไฟล์รูปขนาดใหญ่

  • บางครั้งบางโอกาส เวลาได้รับภาพถ่ายดาวเทียมที่มีขนาดใหญ่ระดับ 500 Megabyte ขึ้นไปต้องการโปรแกรมเล็กๆอะไรสักอย่างที่สามารถเปิดไฟล์ได้อย่างรวดเร็ว ผมขอแนะนำสองโปรแกรมคือ GeoViewer ของ LizardTech และ ER Viewer ของ Erdas เรามาลองวัดดูว่าสองโปรแกรมนี้ใครจะแน่กว่ากัน

Download and Install

  • ER Viewer ขณะที่เขียนอยู่นี้เป็นเวอร์ชั่น 7.2 สามารถดาวน์โหลดได้ที่นี่ http://www.erdas.com/LinkClick.aspx?fileticket=GepwjNjtpmE%3d&tabid=84&mid=401 แต่จะดาวน์โหลดได้ต้อง register ก่อนซึ่งก็ไม่หนักหนาอะไรแค่ป้อนข้อความไม่กี่อย่าง ขนาดดาวน์โหลดประมาณ 17 เมกะไบต์ แล้วทำการติดตั้งได้ง่ายๆ
  • GeoViewer ขณะที่เขียนอยู่นี้เวอร์ชั่น 4.0 ดาวน์โหลดเวอร์ชั่นสำหรับวินโดส์ 32 bit ได้ที่นี่ http://www.lizardtech.com/download/dl_download.php?detail=GeoViewer32 ถ้าเป็นวินโดส์ 64 bit ดาวน์โหลดได้ที่ http://www.lizardtech.com/download/dl_download.php?detail=GeoViewer64 สำหรับ GeoViewer ไม่ต้องลงทะเบียนก็ดาวน์โหลดได้ ตอนแรกจะดาวน์โหลดไฟล์ setup.exe เมื่อทำการคลิกติดตั้งโปรแกรม setup จะดาวน์โหลดโปรแกรมตัวจริงมา ซึ่งมีขนาดประมาณ 20 กว่าเมกกะไบต์
  • ปัญหาของ Geoviewer เมื่อทำการรันครั้งแรกบางเครื่องเช่นเครื่องคอมพิวเตอร์ผมจะเตือนว่า

After installing GeoViewer4 or updating GeoViewer3 to version 4 the client receives the following error box.
“The following exception was thrown while initializing DirectX; a frequent cause is unsupported hardware, drivers, or OS configuration.The 3D view will now be disabled.Exception message: Unable to load DLL ‘LidarRenderer.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)”

GeoViewer

  • มาดูหน้าตาของ Geoviewer ก่อน จะเห็นว่าค่อนข้างเรียบง่าย ก่อนจะใช้งานก็เข้าไปที่เมนู Options ลองตั้งค่าให้ตรงกับความต้องการ

GeoViewer Version 4.0

  • ผมจะทดสอบอ่านไฟล์ satelite image ของ landsat7 รูปแบบ Jpeg ขนาดประมาณ 500 MB ด้วยเมนู File > Add layer > Local layer… ใช้เวลาตั้งแต่อ่านไฟล์เพื่อเปิดภาพจนกระทั่งแสดงผลใช้เวลา 1 นาที 40 วินาที ความรู้สึกผมว่าใช้เวลามากไปหน่อย

GeoViewer เมื่อเปิดไฟล์ jpeg ขนาดประมาณ 500 MB

  • ข้อดีของ GeoViewer คือสามารถอ่านข้อมูลเป็น Shape file หรือ Lidar (*.las) ได้ ถึงจะ support ชนิดข้อมูลไม่มากนัก ข้อดีอีกอย่างคือสามารถ Export ภาพเป็นฟอร์แม็ต GeoTiff, PNG, Jpeg เลือก resolution ได้ วิธีการคือคลิกที่เมนู Tools > Export…

การ export ไฟล์ด้วย GeoViewer

  • ข้อดีที่ผมชอบก็คือแสดง metadata ได้ละเอียดดี ที่เมนูเลือก Tools > View layer metadata…

แสดง metadata ของข้อมูล

  • การแสดงผล 3D ถ้าใช้ข้อมูล Lidar เสียดายผมไม่มีข้อมูลดูรูปจาก Help ของ GeoViewer ไปก่อน

เปิดไฟล์ Lidar (ภาพจาก help ของ GeoViewer)

ER Viewer

  • บอกชื่อของผู้พัฒนาคือ Erdas ก็รับประกันความผิดหวังได้ เมื่อติดตั้งแล้วมาดูหน้าตาของ ER Viewer

ER Viewer version 7.2

  • ลองเปิดไฟล์เดียวกันที่ใช้ GeoViewer เปิด ใช้เวลา 20 วินาที ว้าว…….เร็วมาก แต่ข้อเสียก็มีบ้างแต่ไม่ใช่สาระ เพราะชื่อก็บอกอยู่แล้วว่าเป็น Viewer คือเอาไว้ดูอย่างเดียว ข้อเสียที่ว่าคือไม่สนับสนุน vector file ไม่มี metadata ให้ดูก็ไม่เป็นไร

ER Viewer เปิดภาพไฟล์ขนาดใหญ่

  • สรุปแล้วผมชอบสองโปรแกรมนี้ทั้งคู่ครับ GeoViewer ช้ากว่าแต่ก็มี feature มากกว่าเล็กน้อย
Categories: 3D, GIS, Windows ป้ายกำกับ:, , , , ,

การผสมสี (Band Combination) ภาพถ่ายดาวเทียมด้วยโปรแกรม HighView

  • สำหรับการผสมสี (Band combination) ภาพถ่ายดาวเทียมในตอนนี้ ถือเป็นกรณีศึกษา (case study) ก็แล้วกัน คือภาพถ่ายดาวเทียมแต่ละดวงเช่น Landsat,  Alos, Quickbird, Ikonos, Spot-5 ภาพดั้งเดิมจะแยกเป็น Band กัน ที่เราเห็นส่วนใหญ่จะถูกผสมมาให้เรียบร้อยแล้ว
  • ผมจะขอใช้ภาพถ่ายดาวเทียม Landsat 7 ก็แล้วกันเพราะฟรี ถึงแม้ไม่ค่อยละเอียดนัก การ Download ภาพแบบ Online ด้วยโปรแกรม Global Mapper สะดวกแต่เป็นภาพที่ผสมมาเรียบร้อยแล้ว Band ของภาพถ่ายดาวเทียมเช่น Landsat 7 มีมากถึง 8 Band ภาพถ่ายสีแบบธรรมชาติที่เราเห็นจะใช้ Band 1 (สีน้ำเงิน), Band 2 (สีเขียว) และ Band 3 (สีแดง) มาผสมกัน
  • ผมขอแนะนะ website ที่พูดเรื่องนี้ ดูได้ที่ http://web.pdx.edu/~emch/ip1/bandcombinations.html ตัวอย่างถ้าจะศึกษาเรื่องน้ำ ให้เอา Band 1,4 และ 7 มาผสมกันเป็นต้น
  • ขอแนะนำโปรแกรมตัวเล็กๆคือ Highview ที่เป็นเวอร์ชั่นฟรี สามารถนำมาทำ Band combination ได้เป็นอย่างดี ข้อดีคือใช้งานง่าย ดาวน์โหลดได้ที่ http://www.geosage.com/highview/download/HighView2.5.3.T_GUI.zip โปรแกรมสามารถทำ Pan Sharpening ได้ด้วยแต่ตอนนี้ยังไม่พูดถึง

การ Download ภาพ Landsat 7

  • สามารถดาวน์โหลดได้หลายที่ แต่ที่สะดวกมากที่สุดอยู่ที่ http://glcfapp.umiacs.umd.edu:8080/esdi/index.jsp เปิดเข้าไปจะเห็นทางเลือกการดาวน์โหลดอยู่ 3 แบบเลือกแบบแรกคือ Map search
  • ครั้งแรกจะเห็นแผนที่ทั้งโลกใช้ tool ตัว zoom คลิกที่ประเทศไทยจะได้ภาพขยายตามต้องการ ที่ panel ด้านซ้ายคลิกเลือก ETM+ ที่ด้านล่างภาพเลือกวันที่เดือนปี ที่ต้องการ และอย่าลืมตรงตรง Require เลือก GeoTiff ดีที่สุด
  • ใช้ tool ที่เป็นรูปลูกศรเครื่องหมายบวกเพื่อเลือกภาพโดยการคลิกเข้าไปที่แผนที่ ถ้าบริเวณนั้นมีภาพตามเงื่อนไขที่เราตั้งก็จะปรากฎสีแดง จากนั้นคลิกที่ Preview&Download เพื่อทำการเลือกภาพอีกครั้ง ถ้าบริเวณนั้นมีภาพตรงกับเงื่อนไขซ้อนกันอยู่หลายภาพ

เลือกภาพถ่ายที่ต้องการ

  • จากรูปด้านบนผมเลือกปี 2002 ส่วนภาพปี 2004 ของ USGS พบว่าภาพบริเวณนี้มีปัญหาคือภาพเป็นริ้วๆที่ขอบ

เลือกไฟล์เพื่อดาวน์โหลดขั้นตอนสุดท้าย

  • คลิกตรงไฟล์ตาม Band ที่ต้องการ ชื่อไฟล์จะนำหน้าด้วย path และ row เช่น p130r051_7t20020421_z47_nn10.tif.gz หมายถึง path = 130 row = 51 ตรงกลางชื่อไฟล์แสดงปีคศ. Z47 หมายถึงเป็นภาพ UTM Zone 47 (แน่นอนอยู่บน datum WGS84) ส่วนคำหลัง nn10 จะหมายถึง Band 1
  • การดาวน์โหลดควรใช้โปรแกรมดาวน์โหลดมาช่วยเช่น Flashget, Bitcomet เพราะไฟล์มีขนาดใหญ่ ตอนนี้เราจะดาวน์โหลดมาแค่ 4 ไฟล์ 4 Band ดังรูปด้านบน ส่วน Band 8 เป็นภาพ Panchromatic ซึ่งมีความละเอียดมากกว่าแต่ตอนนี้ ดาวน์โหลดมาก่อนแต่ยังไม่ได้ใช้ จะนำมาใช้อีกครั้งเรื่องการทำ Pan Sharpening เมื่อดาวน์โหลดมาได้เรียบร้อยแล้วทำการ unzip ให้อยู่ในรูปฟอร์แม็ตของ GeoTiff

โปรแกรม HighView

  • ผมชอบเพราะโปรแกรมมีขนาดเล็กๆ ถึงแม้ไม่เป็น opensource แต่ก็ฟรี เมื่อดาวน์โหลดมาแล้วทำการติดตั้งแล้วรันดูจะเห็นหน้าตาที่เรียบง่าย ดังรูปด้านล่าง สังเกตที่เมนูจะเห็นเมนู Band Combination เราจะใช้เมนูนี้เพื่อทำการผสมสี คลิกที่เมนู Band Combination > Landsat ETM+

HighView ของ GeoSage

  • จะเห็น dialog คลิกเลือกไฟล์ตาม Band ดังรูป

เลือกไฟล์ตาม Band เริ่มจาก Band 1, Band 2 และ Band 3 ตามลำดับ

  • จากรูปด้านบนเลือกไฟล์ให้ตรงกับ Band ที่โปรแกรมต้องการเรียงจาก Band 1 Band 2 และ Band 3 อย่าลืมคลิกที่ ที่ผมเน้นไว้เพื่อสร้างไฟล์ GeoTiff ถ้าไม่เลือกโปรแกรมจะผสมภาพให้เป็นภาพ .BMP (ไม่รู้สร้างไฟล์ฟอร์แม็ตนี้มาให้ทำไม ผมเข้าใจว่าเป็นฟอร์แม็ตที่โลกลืมแล้ว) เข้าไปที่ windows explorer แล้วเข้าไปแก้ไขชื่อไฟล์ GeoTiff  ตัวอย่างเช่นโปรแกรมสร้างชื่อให้เป็น p130r051_7t20020421_z47_nn10_Composite.tif ผมแก้ไขเป็น p130r051.tiff ขนาดไฟล์ประมาณ 191 MB ลองเปิดไฟล์ GeoTiff นี้ด้วยโปรแกรม tools ด้าน GIS เช่น Global Mapper จะเห็นสีค่อนข้างทึมๆ ไม่สวย

ไฟล์ GeoTiff เมื่อเปิดดูด้วย Global Mapper

  • จะทำการปรับโทนสีใหม่ให้ดูสดใส ผมเลือกใช้ Gimp เปิดไฟล์ tiff นี้ ต้องระวังหน่อยโปรแกรมจำพวกนี้เช่น Photoshop เมื่อทำการเซฟไฟล์ ความเป็น Georeferencee จะหาย วิธีแก้ไขก็ใช้ Global Mapper ทำการ export เพื่อ Backup ไว้โฟลเดอร์อื่นแล้วอย่าลืม คลิกเลือก Generate TFW (World file) เพื่อสร้าง world file ไว้ เมื่อใช้โปรแกรม Gimp ทำการแก้ไขสี ผมใช้เมนู สี >  levels… ปรับให้สวยแล้วเซฟเป็นไฟล์ใหม่ Gimp จะถามว่าให้จัดเก็บแบบบีบขนาดหรือไม่ ตอบเป็น LZW
  • จากนั้นให้ไป copy world file ที่เรา backup ไว้ด้วย Global Mapper กลับมาที่โฟลเดอร์ไฟล์ที่เราเพิ่งทำการแก้ไขด้วย Gimp ลองเปิดด้วย Global Mapper อีกครั้ง จะสังเกตความเป็น GeoTiff หายไป โปรแกรมจะถามให้เลือก datum เลือก Zone เป็น 47 N เลือก datum เป็น WGS84 หน่วยเป็นเมตร จะเห็น Global Mapper แสดงภาพถ่ายดาวเทียม Landsat 7 ได้สวยงามดังรูปด้านล่าง

ภาพ Geotiff ที่ผ่านการปรับสีแล้ว

  • พูดถึงเรื่องปรับสี แล้วขอเล่าประสบการณ์หน่อย การปรับสีด้วย Gimp หรือ Photoshop (ซึ่งผมเลิกใช้ไปนานแล้ว) ถ้าเป็นภาพใหญ่ๆ ขนาด 1 GB ขึ้นไป ผมมีภาพใหญ่ขนาด 1.4 GB ใช้ Gimp ไม่ประสบความสำเร็จคือจะ hang ไปเลย ส่วน Photoshop เมื่อไปลองที่เครื่องน้องที่สำนักงาน ผ่านครับแต่ก็ช้าพอสมควร ผมว่า Gimp น่าจะมีอะไรปรับปรุงเรื่องอัลกอริทึ่มบ้าง ให้สามารถจัดการภาพไฟล์ขนาดใหญ่นี้ได้ แหมทำเอาผมขายหน้าไปเลย วันนี้เอาแค่นี้ก่อน พบกันตอนหน้าครับ
Categories: GIS, Windows ป้ายกำกับ:, , ,

การใช้ฐานข้อมูล SQLite กับ Lazarus ในเบื้องต้น (ตอนที่ 2)

  • ตอนที่แล้วผม post ตัวโค๊ดทั้งหมด มาดูคำอธิบายตรงสาระที่สำคัญ

Declare ตัวแปรสำหรับ SQLite

  • ที่ class ของ TfrmSetEllipsoid จะ declare เพื่อจัดการกับฐานข้อมูล SQLite ผม declare ทีส่วน private

 { TfrmSetEllipsoid }
 TfrmSetEllipsoid = class(TForm)
    cmdDelete: TButton;
    cmdNew: TButton;
    cmdApply: TButton;
    cmdOK: TButton;
    cmdCancel: TButton;
    dbtxtFlattening: TDBEdit;
    dbtxtSemiMajorAxis: TDBEdit;
    dbtxtEllipsoidCode: TDBEdit;
    DBNavigator1: TDBNavigator;
    dbcboEllipsoidName: TDBComboBox;
    GroupBox1: TGroupBox;
    Image1: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;

    procedure cmdApplyClick(Sender: TObject);
    procedure cmdCancelClick(Sender: TObject);
    procedure cmdDeleteClick(Sender: TObject);
    procedure cmdNewClick(Sender: TObject);
    procedure cmdOKClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
 private
    { private declarations }
    FDBCon : TSQLite3Connection;
    FTrans : TSQLTransaction;
    FSql : TSQLQuery;
    FDatasource : TDatasource;
    FDatabaseFile : string;
    FNewRecordState : boolean;
 public
    { public declarations }
 end;

  • มีตัวแปร FDBCon, FSql, FTrans เป็นตัวแปรในระดับ Dataset ตัวแปร FDBCon อยู่ล่างสุดทำหน้าที่ชี้ไปยัง Database ส่วน FTrans ทำหน้าที่เรื่อง Transaction ส่วน FSql เป็น Dataset ที่ทำหน้าที่ Query ข้อมูลจากฐานข้อมูลตามเงื่อนไขมาเก็บไว้ ส่วน FDatasource อยู่สูงขึ้นมาอีกเป็นตัวกลางระหว่าง Dataset กับวัตถุจำพวก data-aware ที่ทำหน้าที่แสดงฟิลด์ของข้อมูล ดังไดอะแกรมที่ผมแสดงไว้ตอนที่ 1 ความสัมพันธ์ระหว่างตัวแปรนี้เป็นไปดัง event ของ FormCreate

procedure TfrmSetEllipsoid.FormCreate(Sender: TObject);
begin
  //เปลี่ยน path ที่อยู่ของฐานข้อมูลให้ตรงกับโฟลเดอร์ที่เก็บ
  FDatabaseFile := 'e:\sourcecodes\lazarus\sqliteproj2\datums.s3db';

  FDBCon := TSQLite3Connection.Create(NIL);
  FTrans := TSQLTransaction.Create(NIL);
  FSql := TSQLQuery.Create(NIL);
  FDatasource := TDatasource.Create(NIL);

  FDBCon.DatabaseName := FDatabaseFile;
  FDBCon.Transaction := FTrans;
  FTrans.DataBase := FDBCon;
  FSql.DataBase := FDBCon;
  FSql.Transaction := FTrans;
  FDBCon.Connected := true;
  FTrans.Active := true;
  FDatasource.Dataset := FSql;
  FDatasource.AutoEdit := true;

  FSql.Close;
  FSql.SQL.Clear;
  FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
  FSql.Open;
  //FTrans.CommitRetaining; {This line does not work in win32. But OK in linux???}

  dbcboEllipsoidName.DataSource := FDatasource;
  dbcboEllipsoidName.DataField := 'NAME';
  dbtxtEllipsoidCode.DataSource := FDatasource;
  dbtxtEllipsoidCode.DataField := 'CODE';
  dbtxtSemiMajorAxis.DataSource := FDatasource;
  dbtxtSemiMajorAxis.DataField := 'A';
  dbtxtFlattening.DataSource := FDatasource;
  dbtxtFlattening.DataField := 'INV_F';
  DBNavigator1.DataSource := FDatasource;

  FNewRecordState := false;
end;

  • เมื่อปิดโปรแกรมตรง event FormDestroy อย่าลืม Free ให้กับตัวแปรที่เรา Declare ไว้ เพื่อคืนความจำให้ระบบ

procedure TfrmSetEllipsoid.FormDestroy(Sender: TObject);
begin
  FTrans.Free;
  FSql.Free;
  FDatasource.Free;
  //Make sure TSQLite3Connection is the last.
  FDBCon.Free;
end;

ลบ Record

  • การเลื่อน Record ทำได้โดยการลิกที่ปุ่มลูกศร ซ้ายและขวา
  • มาดูการลบ record ที่ปุ่ม (button) “Delete…” สร้าง Event procedure ชี้ไปที่ cmdDeleteClick(Sender: TObject);

procedure TfrmSetEllipsoid.cmdDeleteClick(Sender: TObject);
var
  szECode : string;
  sql : TSQLQuery;

begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szECode := dbtxtEllipsoidCode.Text;

    if (MessageDlg ('คำเตือน','ยืนยันว่าต้องการลบทรงรี', mtConfirmation,
               [mbOK, mbCancel],0) = mrOK) then
    begin
      sql.Close;
      sql.SQL.Text := 'DELETE FROM ELLIPSOIDS WHERE CODE = :CODE';
      sql.Params.ParamByName('CODE').AsString := szECode;
      sql.ExecSQL;
      FSql.ApplyUpdates;
      FTrans.CommitRetaining;
      FSql.Close;
      FSql.SQL.Clear;
      FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
      FSql.Open;
      FTrans.CommitRetaining;
  end;

  finally
    sql.Free;
  end;
end;

  • จากโค๊ดด้านบน เนื่องจากเราไม่ได้ใช้ DBNavigator เราจะำทำการลบด้วยการดึงชื่อโค๊ดของทรงรีเข้าตัวแปร szECode  เพื่อมาใช้ Query “DELETE” ได้ถูก ก่อนจะลบให้ผู้ใช้ยีนยันด้วย MessageDlg

เพิ่มและแก้ไข Record

  • การแก้ไขฟิลด์ของ record ที่มีอยู่แล้วใช้ SQL syntax “UPDATE” แต่การลบ record ใช้ “DELETE” จึงมีตัวแปร FNewRecordState คอยตรวจว่าเป็นการแก้ไขหรือการลบ record มาดูการเพิ่ม record ที่ event “cmdNewClick” ไม่มีอะไรมากสำหรับแค่เตรียมตัว data-aware ให้ว่างและจัดสถานะให้พร้อม พร้อมที่ผู้ใช้โปรแกรมป้อนค่าทรงรีรูปทรงใหม่เข้าไป การจะจัดเก็บเข้า Database ให้ผู้ใช้คลิกที่ปุ่ม “Apply” เพื่อยืนยัน

procedure TfrmSetEllipsoid.cmdNewClick(Sender: TObject);
begin
  //Make its to ready for editable.
  dbcboEllipsoidName.ReadOnly := false;
  dbtxtSemiMajorAxis.ReadOnly := false;
  dbtxtflattening.ReadOnly := false;
  dbtxtEllipsoidCode.ReadOnly := false;
  dbcboEllipsoidName.Text := '';
  dbtxtSemiMajorAxis.Text := '';
  dbtxtFlattening.Text := '';
  dbtxtEllipsoidCode.Text := '';
  cmdApply.Enabled := true;
  cmdNew.Enabled := false;
  DBNavigator1.BtnClick(nbInsert);
  FNewRecordState := true;
end;

UPDATE หรือ INSERT

  • การ update ด้วยโปรแกรม demo ของผมได้แก่การแก้ไขค่าสัณฐานทรงรีเช่นชื่อของทรงรี, ค่า a, ค่า 1/f ยกเว้นไม่ให้แก้ไขคือค่าโค๊ดของทรงรี (CODE) ถ้าเพิ่มรูปทรงรีสามารถทำได้อิสระ เพียงแต่ค่า CODE ของทรงรีต้องไม่ซ้ำกับค่าที่มีอยู่แล้ว
  • เมื่อคลิกปุ่ม “New…” ทำการป้อนรูปทรงรีใหม่เข้าไป หรือแก้ไขค่าทรงรีเดิม ฐานข้อมูลจะไม่มีการเปลี่ยนแปลงจนกระทั่ง ผู้ใช้โปรแกรมคลิกที่ปุ่ม “Apply” โปรแกรมจะทำตรวจสอบว่าผู้ใช้เพิ่ม record จากตัวแปร FNewRecordState ถ้าเป็นการ Update ก็สวิตท์ if clause ไปที่ else if
  • โปรแกรมมีเงื่อนไขอีกนิดว่าค่า a ต้องมากกว่า 6377000 เมตร และค่า 1/f ต้องมากกว่า  293 ถ้าไม่ได้ให้รอป้อนจนป้อนค่าถูก

procedure TfrmSetEllipsoid.cmdApplyClick(Sender: TObject);
var
  szEName, szECode : string;
  dA, dB, dF : extended;
  f1, f2 : boolean;
  sql : TSQLQuery;
begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szEName := trim(dbcboEllipsoidName.Text);
    szECode := trim(dbtxtEllipsoidCode.Text);
    if (szEName <> '') and (szECode <> '') then
    begin
      szECode := Trim(dbtxtEllipsoidCode.Text);
      f1 := trystrtofloat(dbtxtSemiMajorAxis.Text, dA);
      f2 := trystrtofloat(dbtxtFlattening.Text, dF);
      dB := dA - dA * 1 / dF;
      if (FNewRecordState) then
      begin
        if (f1 and (dA > 6377000)) and (f2 and (df > 293)) then
        begin
          sql.Close;
          sql.SQL.Clear;
          sql.SQL.Text := 'INSERT INTO ELLIPSOIDS (NAME,CODE,A,B,INV_F)'
                        + ' VALUES (:NAME,:CODE,:A,:B,:INV_F)';
          sql.Params.ParamByName('NAME').AsString := szEName;
          sql.Params.ParamByName('CODE').AsString := szECode;
          sql.Params.ParamByName('A').AsFloat := dA;
          sql.Params.ParamByName('B').AsFloat := dB;
          sql.Params.ParamByName('INV_F').AsFloat := dF;

          sql.ExecSQL;
          cmdApply.Enabled := False;
          cmdNew.Enabled := true;
          FTrans.CommitRetaining;
          FNewRecordState := false;
        end
        else if not(f1 and (dA > 6377000)) then
        begin
          MessageDlg ('Warning', 'Semi-major axis must be the number and its value must > 6,377,000 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtSemiMajorAxis.SetFocus;
        end
        else if not (f2 and (df > 297)) then
        begin
          MessageDlg ('Warning', 'Flattening must be the number and its value must > 297 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtFlattening.SetFocus;
        end;
      end
      else
      begin
        sql.Close;
        sql.SQL.Clear;
        sql.SQL.Text := 'UPDATE ELLIPSOIDS SET NAME=:NAME,'
                      + 'A=:A,B=:B,INV_F=:INV_F WHERE CODE=:CODE';
        sql.Params.ParamByName('NAME').AsString := szEName;
        sql.Params.ParamByName('CODE').AsString := szECode;
        sql.Params.ParamByName('A').AsFloat := dA;
        sql.Params.ParamByName('B').AsFloat := dB;
        sql.Params.ParamByName('INV_F').AsFloat := dF;

        sql.ExecSQL;
        cmdApply.Enabled := False;
        FTrans.CommitRetaining;

      end;
    end;
    finally
      sql.Free;
    end;
end;

  • มาลองรันโปรแกรม ลองคลิกไปที่ลูกศรขวาจนถึงรูปทรงรี “WGS 72″ ลองคลิกที่ปุ่ม “Delete…”  โปรแกรมจะถามยืนยันตอบ OK

ลองทดสอบลบทรงรี WGS 72

  • เมื่อลบเสร็จโปรแกรมจะเลื่อนไปที่ record แรกอัตโนมัติ ลองเลื่อนลูกศรไปดูว่า WGS 72 ยังอยู่หรือไม่ จะไม่เห็น ต่อไปลองมาแก้ไขค่ารูปทรงรีดู

แก้ไขค่ารูปทรงรี

  • ลองปิดโปรแกรมแล้วรันอีกครั้งทดสอบว่าฐานข้อมูล update ไปตามต้องการแล้วหรือยัง ช่วงทดสอบโปรแกรมผมจะ backup ฐานข้อมูลไว้อีกชุดหนึ่ง ในความเป็นจริงรูปทรงรีที่เป็นมาตรฐาน บางโปรแกรมจะไม่ให้ทำการแกัไข ที่จะแก้ไขได้แก่รูปทรงรีที่ผู้ใช้เพิ่มเข้าไป (user defined) ฉะนั้นเวลาโปรแกรมกันจริงๆ ฐานข้อมูลเราจะเพิ่มฟิลด์ READONLY ไปอีกฟิลด์หนึ่ง ถ้าเป็นรูปทรงรีมาตรฐาน READONLY จะเป็น TRUE
  • ฐานข้อมูลที่ทดสอบเป็นไปอย่างง่ายมีแค่ Table เดียว ถ้ามีหลาย Table เช่นการเขียนโปรแกรมแปลงค่าพิกัดที่ support หลายๆเส้นโครงแผนที่ (Map projection) ในฐานข้อมูลนี้จะจัดเก็บ datum ไว้อีก Table หนึ่งต่างหาก  ซึ่งจะมีค่าพารามิเตอร์ เช่น Translation แกน X, Y, Z และค่า Rotation แกน X, Y, Z และมึค่า Scale รวมแล้วเป็นเจ็ดพารามิเตอร์

Download sourcecode

  • sourcecode ของ post ตอนนี้สามารถ download ได้ที่ SQLiteProj2.zip

ทิ้งท้ายก่อนจาก

  • เป็นธรรมเนียมก่อนจะจบ post แต่ละตอนจะมีอะไรมาพูดคุยก่อนจะจาก ตอนที่แล้วผมพูดถึงไลบรารี ด้าน GIS คือ GDAL/OGR ที่ website จะเห็นมีตัว wrapper ด้วยภาษาอื่นๆที่ไม่ใช่ภาษา C++ เช่น Python, Ruby, Java, Perl และ C#/.Net การจะ wrapper ส่วนใหญ่จะตามมาตรฐานที่เรียกว่า SWIG (Simplified Wrapper and Interface Generator) มีกลุ่มโปรแกรมเมอร์ที่ตั้งมาตรฐานในการเขียนโปรแกรมเพื่อ interface กับ Library ที่เขียนด้วย C++ ด้วยภาษาอื่นๆที่ผมกล่าวมาแล้ว จะเห็นว่าขาด Delphi หรือ Lazarus และ VB (อีกแล้ว)
  • มีโปรแกรมเมอร์ชื่อ Stefano Moratto เขียน interface GDAL ด้วย SWIG ที่เขาเขียนด้วยตัวเขาเอง แต่ปรากฎว่าตั้งแต่ปี 2008 คุณ Frank Warmerdam ยังตามตัว Stefano Moratto ไม่เจอ ดูที่นี่ แหมน่าเสียดายมาก ถ้ามี SWIG ของ GDAL ด้วย Delphi ก็วิเศษเลย จะเป็นประโยชน์แก่โปรแกรมเมอร์ Delphi อีกมากรวมทั้ง Lazarus ด้วย ไม่เป็นไรตัว wrapper ที่เขียนด้วย VB ที่ผมแปลงโค๊ดเป็น Lazarus ก็พอไหว ตอนนี้แค่นี้ก่อนครับ
Categories: Lazarus, Linux, Programming, Windows ป้ายกำกับ:, ,

การใช้ฐานข้อมูล SQLite กับ Lazarus ในเบื้องต้น (ตอนที่ 1)

  • ตอนก่อนหน้านี้ผมพูดเรื่อง SQLite พร้อมทั้ง tools สำหรับ admin ที่หาได้ในวินโดส์ พร้อมทั้งการ Pump ข้อมูลจากไฟล์ CSV เข้าฐานข้อมูล ต่อไปนี้จะมาดูลึกเข้าไปอีกนิด จะมาเขียนโปรแกรมเพื่อติดต่อกับฐานข้อมูล ด้วย Lazarus
  • ปัญหาของการพัฒนาโปรแกรมฐานข้อมูล SQLite ด้วย Lazarus ก็คือถ้าใช้ object จำพวก Data-aware(แทนฟิลด์)  เช่น  DBNavigator, DBEdit, DBGrid เป็นต้นพวกนี้อิงอยู่กับ Datasource และ Datasource อ้างอิงไปที่ชุด Dataset เช่น Table, Query, StoreProc และสุดท้าย Dataset อ้างอิงไปยัง Database ดูรูปโมเดลด้านล่างแสดงความสัมพันธ์

Database components model.

  • แต่ปัญหาของ SQLite  บน Lazarus นั้นเท่าที่พบใน Lazarus คือใช้ data-aware เช่น วาง DBNavigator, DBEdit, DBGrid ลงบนฟอร์ม วาง Datasource และวาง Dataset คือ TSQLite3Dataset ป้อนชื่อไฟล์ฐานข้อมูลให้กับ TSQLite3Dataset เมื่อทำการแก้ไขเช่นแก้ไขค่าในฟิลด์ ลบ record ทำการ Post ด้วย DBNavigator แต่เมื่อเปิดโปรแกรมมาใหม่ ข้อมูลจะไม่ update ยังเหมือนเดิม อืม………..ผมยังไม่รู้ว่าเป็นที่ Components ติดต่อฐานข้อมูลของ Lazarus หรือว่าเป็นที่ Library ของ SQLite แต่ความคิดผมน่าจะเป็นที่ Lazarus วิธีนี้ไม่ work
  • เอาใหม่วิธีนี้ต้องเขียน code มากขึ้น แต่ถ้าเข้าใจจะเป็นวิธีที่มีประสิทธิภาพมากที่สุด คือสามารถควบคุม บริหารฐานข้อมูลได้ลึกซึ้งกว่า และถ้าเปลี่ยนฐานข้อมูลจาก SQLite ไปเป็นฐานข้อมูลอื่นๆ จะแก้ code น้อยมาก เพราะโ้ค้ดทั้งหมดจะเป็นภาษา SQL ผมเขียนหัวข้อว่าเบื้องต้น แต่ในความเป็นจริงก็ไม่ยากเพราะภาษา SQL เป็นภาษามาตรฐานและง่ายต่อการเรียนรู้

เตรียมฐานข้อมูล SQLite

  • ตอนก่อนหน้านี้ ที่ผมแนะนำ tools ไปแล้วคือ SQLite Administrator เราสร้างไฟล์ฐานข้อมูล ผมตั้งชื่อว่า datums.s3db ตอนนี้มี table อยู่ 1 table คือ Ellipsoidas อนาคตเราจะสร้าง table เพิ่มคือ เส้นโครงแผนที่ (Map Projection) เข้ามาอยู่ด้วยกัน ดูรูปด้านล่าง

SQLite Administrator จัดการฐานข้อมูล

สร้าง New Project

  • เปิด Lazarus คลิกที่เมนู Project > New Project… เลือก Application วาง object เช่น ปุ่ม New…, Delelte…, Apply เป็นต้น ต่อจากนั้น  เราจะวางวัตถุจำพวก data-aware ตามปกติเช่น DBCombobox, DBText, DBEdit แล้ววาง DBNavigator ดูรูปด้านล่างประกอบ

วาง object เช่น controls data-aware ตั้งชื่อดังรูป

  • ตั้งชื่อ unit เป็น main ตั้งชื่อฟอร์มว่า frmSetEllipsoid แล้วแก้ไข uses ดังโค๊ดด้านล่าง อย่าลืมติดตั้งสอง component ที่ผมเคยกล่าวไปแล้วคือ sqlitedb.lpk กับ sqlitelaz.lpk ด้วยเมนูหลักคือ Package > Open package file (*.lpk) ซึ่งสอง component นี้อยู่ในโฟลเดอร์ c:\lazarus\components\sqldb และ c:\lazarus\components\sqlite ตามลำดับ

unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, sqldb, DbCtrls, db, SQLite3Conn;

จุดประสงค์ของโปรแกรม

  • ต้องการอ่านฐานข้อมูล SQLite ที่เตรียมไว้มาแสดงบน data-aware ต่างๆ เช่น DBCombobox, DBEdit แล้วทำการ Edit ข้อมูลเช่นเพิ่ม record ลบ record ด้วยการเขียนโค๊ดคุมเป็นหลัก ต่อไปผมจะ post โค๊ดทั้งหมดที่อยู่บนยูนิต main (main.pas)

unit main;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
  StdCtrls, ExtCtrls, sqldb, DbCtrls, db, SQLite3Conn;

type

  { TfrmSetEllipsoid }

  TfrmSetEllipsoid = class(TForm)
    cmdDelete: TButton;
    cmdNew: TButton;
    cmdApply: TButton;
    cmdOK: TButton;
    cmdCancel: TButton;
    dbtxtFlattening: TDBEdit;
    dbtxtSemiMajorAxis: TDBEdit;
    dbtxtEllipsoidCode: TDBEdit;
    DBNavigator1: TDBNavigator;
    dbcboEllipsoidName: TDBComboBox;
    GroupBox1: TGroupBox;
    Image1: TImage;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;

    procedure cmdApplyClick(Sender: TObject);
    procedure cmdCancelClick(Sender: TObject);
    procedure cmdDeleteClick(Sender: TObject);
    procedure cmdNewClick(Sender: TObject);
    procedure cmdOKClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { private declarations }
    FDBCon : TSQLite3Connection;
    FTrans : TSQLTransaction;
    FSql : TSQLQuery;
    FDatasource : TDatasource;
    FDatabaseFile : string;
    FNewRecordState : boolean;
  public
    { public declarations }
  end;

var
  frmSetEllipsoid: TfrmSetEllipsoid;

implementation

{ TfrmSetEllipsoid }

procedure TfrmSetEllipsoid.cmdOKClick(Sender: TObject);
begin
  FSql.Close;
  FDBCon.Close;
  FTrans.EndTransaction;
  Close;
end;

procedure TfrmSetEllipsoid.FormClose(Sender: TObject;
  var CloseAction: TCloseAction);
begin
  FTrans.EndTransaction;
  FSql.Close;
  FDBCon.Close;
end;

procedure TfrmSetEllipsoid.FormCreate(Sender: TObject);
begin
  //เปลี่ยน path ที่อยู่ของฐานข้อมูลให้ตรงกับโฟลเดอร์ที่เก็บ
  FDatabaseFile := 'e:\sourcecodes\lazarus\sqliteproj2\datums.s3db';

  FDBCon := TSQLite3Connection.Create(NIL);
  FTrans := TSQLTransaction.Create(NIL);
  FSql := TSQLQuery.Create(NIL);
  FDatasource := TDatasource.Create(NIL);

  FDBCon.DatabaseName := FDatabaseFile;
  FDBCon.Transaction := FTrans;
  FTrans.DataBase := FDBCon;
  FSql.DataBase := FDBCon;
  FSql.Transaction := FTrans;
  FDBCon.Connected := true;
  FTrans.Active := true;
  FDatasource.Dataset := FSql;
  FDatasource.AutoEdit := true;

  FSql.Close;
  FSql.SQL.Clear;
  FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
  FSql.Open;
  //FTrans.CommitRetaining; {This line does not work in win32. But OK in linux???}

  dbcboEllipsoidName.DataSource := FDatasource;
  dbcboEllipsoidName.DataField := 'NAME';
  dbtxtEllipsoidCode.DataSource := FDatasource;
  dbtxtEllipsoidCode.DataField := 'CODE';
  dbtxtSemiMajorAxis.DataSource := FDatasource;
  dbtxtSemiMajorAxis.DataField := 'A';
  dbtxtFlattening.DataSource := FDatasource;
  dbtxtFlattening.DataField := 'INV_F';
  DBNavigator1.DataSource := FDatasource;

  FNewRecordState := false;
end;

procedure TfrmSetEllipsoid.FormDestroy(Sender: TObject);
begin
  FTrans.Free;
  FSql.Free;
  FDatasource.Free;
  //Make sure TSQLite3Connection is the last.
  FDBCon.Free;
end;

procedure TfrmSetEllipsoid.cmdCancelClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmSetEllipsoid.cmdDeleteClick(Sender: TObject);
var
  szECode : string;
  sql : TSQLQuery;

begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szECode := dbtxtEllipsoidCode.Text;

    if (MessageDlg ('คำเตือน','ยืนยันว่าต้องการลบทรงรี', mtConfirmation,
               [mbOK, mbCancel],0) = mrOK) then
    begin
      sql.Close;
      sql.SQL.Text := 'DELETE FROM ELLIPSOIDS WHERE CODE = :CODE';
      sql.Params.ParamByName('CODE').AsString := szECode;
      sql.ExecSQL;
      FSql.ApplyUpdates;
      FTrans.CommitRetaining;
      FSql.Close;
      FSql.SQL.Clear;
      FSql.SQL.Text := 'SELECT * FROM ELLIPSOIDS';
      FSql.Open;
      FTrans.CommitRetaining;
  end;

  finally
    sql.Free;
  end;
end;

procedure TfrmSetEllipsoid.cmdNewClick(Sender: TObject);
begin
  //Make its to ready for editable.
  dbcboEllipsoidName.ReadOnly := false;
  dbtxtSemiMajorAxis.ReadOnly := false;
  dbtxtflattening.ReadOnly := false;
  dbtxtEllipsoidCode.ReadOnly := false;
  dbcboEllipsoidName.Text := '';
  dbtxtSemiMajorAxis.Text := '';
  dbtxtFlattening.Text := '';
  dbtxtEllipsoidCode.Text := '';
  cmdApply.Enabled := true;
  cmdNew.Enabled := false;
  DBNavigator1.BtnClick(nbInsert);
  FNewRecordState := true;
end;

//The big problem that DBNavigator1.Post method doesn't work.
//using query to solve the problem with ExeSql method.
procedure TfrmSetEllipsoid.cmdApplyClick(Sender: TObject);
var
  szEName, szECode : string;
  dA, dB, dF : extended;
  f1, f2 : boolean;
  sql : TSQLQuery;
begin
  try
    sql := TSQLQuery.Create(nil);
    sql.Database := FDBCon;
    sql.Transaction := FTrans;
    szEName := trim(dbcboEllipsoidName.Text);
    szECode := trim(dbtxtEllipsoidCode.Text);
    if (szEName <> '') and (szECode <> '') then
    begin
      szECode := Trim(dbtxtEllipsoidCode.Text);
      f1 := trystrtofloat(dbtxtSemiMajorAxis.Text, dA);
      f2 := trystrtofloat(dbtxtFlattening.Text, dF);
      dB := dA - dA * 1 / dF;
      if (FNewRecordState) then
      begin
        if (f1 and (dA > 6377000)) and (f2 and (df > 293)) then
        begin
          sql.Close;
          sql.SQL.Clear;
          sql.SQL.Text := 'INSERT INTO ELLIPSOIDS (NAME,CODE,A,B,INV_F)'
                        + ' VALUES (:NAME,:CODE,:A,:B,:INV_F)';
          sql.Params.ParamByName('NAME').AsString := szEName;
          sql.Params.ParamByName('CODE').AsString := szECode;
          sql.Params.ParamByName('A').AsFloat := dA;
          sql.Params.ParamByName('B').AsFloat := dB;
          sql.Params.ParamByName('INV_F').AsFloat := dF;

          sql.ExecSQL;
          cmdApply.Enabled := False;
          cmdNew.Enabled := true;
          FTrans.CommitRetaining;
          FNewRecordState := false;
        end
        else if not(f1 and (dA > 6377000)) then
        begin
          MessageDlg ('Warning', 'Semi-major axis must be the number and its value must > 6,377,000 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtSemiMajorAxis.SetFocus;
        end
        else if not (f2 and (df > 297)) then
        begin
          MessageDlg ('Warning', 'Flattening must be the number and its value must > 297 m.?', mtConfirmation,
                     [mbOK],0);
          dbtxtFlattening.SetFocus;
        end;
      end
      else
      begin
        sql.Close;
        sql.SQL.Clear;
        sql.SQL.Text := 'UPDATE ELLIPSOIDS SET NAME=:NAME,'
                      + 'A=:A,B=:B,INV_F=:INV_F WHERE CODE=:CODE';
        sql.Params.ParamByName('NAME').AsString := szEName;
        sql.Params.ParamByName('CODE').AsString := szECode;
        sql.Params.ParamByName('A').AsFloat := dA;
        sql.Params.ParamByName('B').AsFloat := dB;
        sql.Params.ParamByName('INV_F').AsFloat := dF;

        sql.ExecSQL;
        cmdApply.Enabled := False;
        FTrans.CommitRetaining;

      end;
    end;
    finally
      sql.Free;
    end;
end;

initialization
  {$I main.lrs}

end.

  • ติดตามตอนหน้าผมจะอธิบายโค๊ดที่สำคัญ เราจะทดลองเพิ่มและลบ record ที่อยู่ในฐานข้อมูลดู

ทิ้งท้ายก่อนจากกัน

  • ที่ผ่านมาผมเขียนถึงเรื่อง GIS Components ที่จะหานำมาพัฒนาโปรแกรม Desktop application ด้าน GIS สุดท้ายมาพบกับ Quantum GIS แต่มีปัญหาว่าโค๊ดเป็นภาษา C++ ซึ่งการแปลง header file ของ C++ ไม่ใช่เรื่องง่ายสำหรับผมซึ๋งไม่กระดิก C++ เลย และจำนวนไฟล์ header ก็มีมาก เลยลดเกรดลงมาดูจำพวก library ทีี่เกี่ยวข้องกับด้าน GIS ที่ดังๆ ก็คือ GDAL/OGR ชื่อเต็มๆก็คือ Geospatial Data Abstraction Library ที่จริงรู้จักตั้งนานแล้วแต่ไม่ใช่ในฐานะโปรแกรมมิ่ง GDAL/OGR ถูกนำไปใช้ด้วยโปรแกรมดังๆมากมาย เช่น ArcGIS, Google Earth, Minisota Mapserver, MapWindow, FWTools,QGIS และอื่นๆอีกหลายโปรแกรม เป็น library ที่นำมาจัดการกับพวก Raster file (GDAL) และ Vector file (OGR) ผมดูใน website ของ GDAL พบว่ามีคนเขียนด้วย VB6 ด้วยการ wrap ขึ้นเป็น class ผมนำมาแปลงเป็นโค๊ดของ Lazarus ช่วงนี้อยู่ระหว่างการทดสอบ แต่ก็ผ่านไปแล้วประมาณ 80% ว่างๆจะนำมาแนะนำกัน
  • คือในโลกของ Opensource เป็นโลกแห่งการ share กัน และร่วมพัฒนาต่อยอดขึ้นไปอีก GDAL/OGR ทีมพัฒนานำโดยคุณ Frank Warmerdam เป็นคนที่มีชื่อเสียงมาก ผมลองค้นคำว่า Frank Warmerdam GDAL บน Google พบจำนวน record ทั้งหมดประมาณ 75,000 ไม่ธรรมดาเลย ต่อไปลองไปดู plunins ของ Quantum GIS ที่ใช้ไลบรารีของ GDAL/OGR ที่ผมเปิดให้ดูเป็น OGR ใช้แปลง vector file ผมลองแปลงจาก Shape file ไปเป็น Sqlite แล้วเปิดด้วย QGIS ก็สามารถเปิดได้ ดูรูปด้านล่าง

การแปลง vector file ด้วยการใช้ plugins ของ GDAL/OGR บน QGIS

  • ตอนต่อๆไปผมจะำนำไลบรารี GDAL/OGR มารันด้วย Lazarus และจะลองเขียนโปรแกรมแปลงฟอร์แม็ตของ Raster/Vector ดู ด้วยการใช้สุดยอดไลบรารีตัวนี้ครับ
Categories: Lazarus, Linux, Programming, Windows ป้ายกำกับ:, ,
Follow

Get every new post delivered to your Inbox.

Join 26 other followers