คอมไพล์ Python Script เป็นไฟล์ Executable ด้วย PyInstaller

PyInstaller

คือเครื่องมือที่ช่วยการแปลงโปรแกรมที่เขียนด้วยไพทอนเป็น execute binary file  ที่สามารถนำไปรันได้โดยที่เครื่องคอมพิวเตอร์ปลายทางไม่ต้องติดตั้งไพทอน สำหรับ PyInstaller เป็น cross-platform สามารถใช้งานได้บนวินโดส์ แมค และลีนุกซ์ สนับสนุนไพทอนรุ่น 2.7 และ ไพทอน รุ่น 3.3 ถึง 3.6 จุดมุ่งหมายของ PyInstaller คือต้องการช่วยผู้ใช้ในการแปลงโปรแกรมไพทอน ที่ใช้โมดูลไลบรารีภายนอกเช่น Matplotlib, DJango, wxPython, PyQt เป็นต้น ให้สามารถทำได้ง่ายสะดวก

ติดตั้ง PyInstaller

ติดตั้งง่ายๆด้วยคำสั่ง pip ใน command prompt

pip install pyinstaller

ใช้งาน PyInstaller

การใช้งานสามารถใช้งานผ่าน command line ได้ แต่สำหรับโปรแกรมที่เรียกใช้โมดูลไลบรารีข้างนอกและต้องขนข้อมูล (data) ที่โมดูลไลบรารีนั้นๆต้องการใช้  ผมแนะนำให้ใช้ไฟล์สคริปท์ (Spec file) มาช่วยจะดีกว่า ปรับแต่งได้มากกว่า ตัว spec file จริงๆก็คือไฟล์สคริปท์ของไพทอนนั่นเอง กรณีที่ต้องใช้ Spec file อีกกรณีหนึ่งคือต้องการขนรันไทม์ไลบรารีเช่น .dll หรือ .so ไปแบบแมนวล กรณีที่ผมเจอคือผมใช้ PySide2 ที่รุ่นทางการจริงๆยังไม่ออกมา แต่ hook file ก็มีมาให้แล้วพร้อมกับ PyInstaller รุ่นใหม่ 3.3 แต่ผมใช้งานแล้วยังไม่สำเร็จ ดังนั้นจึงต้องใช้ Spec file นี้เป็นตัวช่วยในการขนรันไทม์ไลบรารีไป ส่วนเรื่อง hook file คืออะไรค่อยว่าอีกที

กรณีศึกษาด้วย Surveyor Pocket Tools บนวินโดส์

โปรแกรม Surveyor Pocket Tools พัฒนาด้วยไพทอน ปัจจุบันใช้ไพทอน รุ่น 3.6 ใช้โมดูลไลบรารีข้างนอกคือ openpyxl, pyproj, geographiclib, gmplot, simplekml, pyshp และที่ขาดไม่ได้คือ PySide2 ซึ่งสำหรับ openpyxl และ pyproj จะมีการขนข้อมูลไปด้วย ส่วน PySide2 ผมจะขนไฟล์ dll  ที่ต้องการด้วยมือล้วนๆ

Spec file ของ Surveyor Pocket Tools

มาดูไฟล์สคริปนี้ ผมตั้งชื่อว่า “setup.spec”

# -*- mode: python -*-
# -*- mode: python -*-
import sys
import PySide2
import os
block_cipher = None
 
dirname = os.path.dirname(PySide2.__file__)
plugins_path = os.path.join(dirname, 'plugins', '')
 
pyside2_plugins = [(plugins_path + 'iconengines/*', 'plugins/iconengines/'),
                  (plugins_path + 'imageformats/*', 'plugins/imageformats/'),
		  (plugins_path + 'platforms/*', 'plugins/platforms/'),
		  (plugins_path + 'printsupport/*', 'plugins/printsupport/'),
		  (plugins_path + 'sqldrivers/*', 'plugins/sqldrivers/')]
 
added_files = [('markers/*', 'markers/'),
               ('geoids/*', 'geoids/'),
	       ('database/*', 'database/'),
	       ('example data/*', 'example data/'),
	       ('qt.conf', ''), 
	       ('*.xml', '')]
 
a = Analysis(['main.py'],
             pathex=['D:\\sourcecodes\\python\\surveyor pocket tools'],
             binaries=None,
             datas=added_files + pyside2_plugins,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          exclude_binaries=True,
          name='surveyor pocket tools',
		  icon='Land Surveying-64.ico',
          debug=False,
          strip=False,
          upx=False,
          console=False )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='setup')

ลองมาดูโค้ดกัน เริ่มจาก import PySide2 เข้ามาเพื่อจะตรวจสอบว่า PySide2 ที่เราใช้งานเป็น 32 บิตหรือ 64 บิต เพื่อจะได้ขน .dll ไปถูกรุ่น จากนั้นเก็บไดเรคทอรีของ PySide2 เข้าเก็บใน dirname ผ่านฟังก์ชัน os.path.dirname() ที่นี้เราทราบว่าในไดเรคทอรีของ PySide2 จะมีไดเรคทอรีย่อยชื่อ “plugins” อยู่ ทำการเก็บไดเรคทอรีนี้ด้วยฟังก์ชั่น os.path.join() ไปเก็บไว้ในตัวแปร plugins_path

# -*- mode: python -*-
import sys
import PySide2
import os
block_cipher = None
 
dirname = os.path.dirname(PySide2.__file__)
plugins_path = os.path.join(dirname, 'plugins', '')

ต่อไปคือตัวแปร pyside2_plugins จะเป็นลิสต์เก็บ tuple โดยสมาชิกตัวแรกจะเก็บชื่อไฟล์ไดเรคทอรีต้นทาง ใช้เครื่องหมาย * เพราะต้องการทุกๆไฟล์ในไดเรคทอรีนี้ สมาชิกตัวที่สอง จะเก็บชื่อไดเรคทอรีปลายทางที่ต้องการไฟล์เหล่านี้ไปอยู่

 
pyside2_plugins = [(plugins_path + 'iconengines/*', 'plugins/iconengines/'),
                   (plugins_path + 'imageformats/*', 'plugins/imageformats/'),
		   (plugins_path + 'platforms/*', 'plugins/platforms/'),
		   (plugins_path + 'printsupport/*', 'plugins/printsupport/'),
		   (plugins_path + 'sqldrivers/*', 'plugins/sqldrivers/')]

ลองมาดูว่าไดเรคทอรี “plugins” ผมไฮไลท์ไว้เฉพาะไดเรคทอรีที่โปรแกรม Surveyor Pocket Tools ต้องการ

ต่อไปจะขนไฟล์ที่โปรแกรม Surveyor Pocket Tools ต้องการใช้ ให้ใส่ไว้ที่ตัวแปร added_files โครงสร้างเป็น tuple เหมือนกัน และขนไฟล์ชื่อ qt.conf ที่ PySide2 ต้องการไปด้วย

added_files = [('markers/*', 'markers/'),
               ('geoids/*', 'geoids/'),
	       ('database/*', 'database/'),
	       ('example data/*', 'example data/'),
	       ('qt.conf', ''), 
	       ('*.xml', '')]

มาดูไดเรคทอรีที่โปรแกรมต้องการดังนี้

ต่อไปมาดูโค้ดส่วนที่สำคัญมาก ‘main.py’ คือไฟล์สคริปท์หลักของโปรแกรม Surveyor Pocket Tools ต่อไปคือ pathex เป็นไดเรคทอรีของไฟล์ไพทอนสคริปท์ และ datas ที่ผมจัดการรวม added_files และ pyside2_plugins เข้าด้วยกัน สุดท้าย hookspath คือไดเรคทอรีที่เก็บไฟล์ hook ไว้ สำหรับไฟล์ hook นี้ PyInstaller จะอ่านสคริปท์นี้ทีละไฟล์มาตัดสินใจว่าจะขนข้อมูลไดเรคทอรีไหนไป ผมเลือกใช้ดีฟอลท์ครับคือปล่อยว่าง

a = Analysis(['main.py'],
             pathex=['D:\\sourcecodes\\python\\surveyor pocket tools'],
             binaries=None,
             datas=added_files + pyside2_plugins,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher)

สำหรับไดเรอทอรี hooks ที่เป็นดีฟอลท์มากับ PyInstaller ผมใช้ไฟล์เพียงสองไฟล์เท่านั้น ตามที่ไฮไลท์ไว้

ใช้ PyInstaller คอมไพล์ไฟล์ setup.spec

ผมใช้ Minoconda เมื่อจะคอมไพล์ก็เรียก command prompt มาดังนี้ ใช้คำสั่ง cd เข้ามาที่พาทของสคริปท์ของไพทอน ใช้คำสั่ง dir ดูไฟล์ setup.spec

ต่อไปทำการคอมไพล์ ด้วยคำสั่ง

pyinstaller setup.spec

ผลลัพธ์ของ PyInstaller

เมื่อคอมไพล์เสร็จแล้ว ไม่มี error จะได้ไดเรคทอรีมาสองคือ “build” และ “dist” เมื่อเข้าไปดูใน “dist” จะเห็นไดเรคทอรีย่อยช “setup” ชื่อไดเรคทอรีนี้ PyInstaller จะสร้างตามชื่อหน้าของไฟล์ setup.spec เมื่อเข้าดูที่ไดเรคทอรี “setup” จะเห็นไฟล์ต่างๆที่โปรแกรมต้องการ

ผมลองดับเบิ้ลคลิกไฟล์ “surveyor pocket tools.exe” ก็สามารถเปิดมาและทำงานได้ตามปกติ ลองดูชื่อไดเรคทอรีจะเห็นสองไดเรคทอรี ที่ได้จากไฟล์ hooks คือ openpyxl และ pyproj ลองเข้าไปดูในไดเรคทอรี จะเห็นข้อมูลที่ pyproj ขนไปใช้ หมายเหตุว่าข้อมูลนี้ pyproj จะนำไปเป็นฐานข้อมูลในการแปลงพิกัดตาม datum และ projection

ทำไฟล์ Setup ด้วย Inno Setup

จากนั้นผมจะ copy ไดเรคทอรีที่อยู่ใน “setup” ไปไว้อีกที่หนึ่ง พื้นที่นี้สำหรับใช้ Inno Setup มาทำไฟล์ติดตั้ง ลองดูไดเรคทอรี

ในไดเรคทอรีนี้ผมจะมีไฟล์ “surveyorpockettools64.iss” เป็นไฟล์สคริปท์ของ Inno Setup เพื่อสร้างไฟล์ติดตั้ง setup สำหรับวินโดส์ 64 บิต

#define MyAppName "Surveyor Pocket Tools"
#define MyAppEXE "Surveyor Pocket Tools.exe"
#define MyShortAppName "SurveyorPocketTools"
#define MyMainRoot "Survey Suite"
#define Developer "Prajuab Riabroy"
#define Version "0.98"
#define Build "573"
 
[Setup]
AppName={#MyAppName}
AppVerName={#MyAppName} V{#Version}
DefaultDirName={pf}\{#MyMainRoot}\{#MyAppName}
DefaultGroupName={#MyMainRoot}\{#MyAppName}
UseSetupLdr=yes
UninstallDisplayIcon={app}\{#MyAppEXE}
VersionInfoProductName={#MyAppName}
VersionInfoCompany=priabroy
VersionInfoCopyright=Copyright 2000-2017 by {#Developer}
VersionInfoDescription={#MyAppName}
VersionInfoProductVersion={#Version}
VersionInfoVersion={#Version}
OutputDir=Setup
OutputBaseFilename={#MyShortAppName}V{#Version}Build{#Build}Setup64
;OutputDir=TraverseProV250Setup64
; "ArchitecturesAllowed=x64" specifies that Setup cannot run on
; anything but x64.
ArchitecturesAllowed=x64
; "ArchitecturesInstallIn64BitMode=x64" requests that the install be
; done in "64-bit mode" on x64, meaning it should use the native
; 64-bit Program Files directory and the 64-bit view of the registry.
;ArchitecturesInstallIn64BitMode=x64
ArchitecturesInstallIn64BitMode=x64
AppPublisher={#Developer}
AppPublisherURL=https://www.surveyorpockettools.org
AppVersion={#Version}.{#Build}
LicenseFile = eula.txt
ChangesEnvironment=yes
SolidCompression=yes
Compression=lzma2/ultra64
LZMAUseSeparateProcess=yes
LZMADictionarySize=1048576
LZMANumFastBytes=273
 
[Files]
Source: "{#MyAppName}.exe"; DestDir: "{app}"
Source: "base_library.zip"; DestDir: "{app}" ;
Source: "plugins\*"; DestDir: "{app}\plugins\"; Flags: ignoreversion recursesubdirs
Source: "database\*"; DestDir: "{userappdata}\{#MyAppName}\database\";
Source: "geoids\*"; DestDir: "{userappdata}\{#MyAppName}\geoids\";
Source: "markers\*"; DestDir: "{userappdata}\{#MyAppName}\markers\";
Source: "example data\*"; DestDir:"{userappdata}\{#MyAppName}\example data\";
Source: "pyproj\data\*"; DestDir: "{app}\pyproj\data\";
Source: "openpyxl\*"; DestDir: "{app}\openpyxl\";
;Source: "requests\*"; DestDir: "{app}\requests\";
Source: "*.html"; DestDir: "{userappdata}\{#MyAppName}\";
Source: "*.dll"; DestDir: "{app}"
Source: "*.pyd"; DestDir: "{app}"
Source: "*.xml"; DestDir: "{userappdata}\{#MyAppName}\";
Source: "qt.conf"; DestDir: "{app}"
 
[Icons]
;create icon at start menu group
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExe}"
;create icon at desktop
Name: "{commondesktop}\{#MyAppName}"; FileName:"{app}\{#MyAppExe}"
 
[Registry]
; Start "Software\My Company\My Program" keys under HKEY_CURRENT_USER
; and HKEY_LOCAL_MACHINE. The flags tell it to always delete the
; "My Program" keys upon uninstall, and delete the "My Company" keys
; if there is nothing left in them.
Root: HKCU; Subkey: "Software\{#MyMainRoot}"; Flags: uninsdeletekeyifempty
Root: HKCU; Subkey: "Software\{#MyMainRoot}\{#MyAppName}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\{#MyMainRoot}"; Flags: uninsdeletekeyifempty
Root: HKLM; Subkey: "Software\{#MyMainRoot}\{#MyAppName}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\{#MyMainRoot}\{#MyAppName}\Settings"; ValueType: string; ValueName: "InstalledPath"; ValueData: "{app}"
Root: HKLM; Subkey: "Software\{#MyMainRoot}\{#MyAppName}\Settings"; ValueType: string; ValueName: "DevelopedBy"; ValueData: "{#Developer}"
Root: HKLM; Subkey: "Software\{#MyMainRoot}\{#MyAppName}\Settings"; ValueType: string; ValueName: "ApplicationName"; ValueData: "{#MyAppName}"
;Root: HKCU; Subkey: "Environment"; ValueType:string; ValueName:"PROJ_LIB"; ValueData:"{userappdata}\{#MyAppName}\geoidgrids\" ; Flags: preservestringtype ;

ถ้าเป็นไฟล์สำหรับ Surveyor Pocket Tools รุ่น 32 บิตเพียงใส่คอมเมนต์หน้า ;ArchitecturesInstallIn64BitMode=x64 ก็พอ สำหรับรายละเอียดสคริปท์ของ Inno Setup ผมจะไม่กล่าวถึงรายละเอียดในที่นี้ผู้อ่านที่สนใจสามารถศึกษาได้ครับ จากนั้นก็ใช้ Inno Setup ทำการ build ก็จะได้ไฟล์ Exe เดี่ยวๆ ที่สามารถ zip ไปให้ผู้ใช้ได้ download ต่อไป พบกันตอนหน้าครับ

ปลดพันธนาการ PyQt5 ด้วย PySide2

ตอนนี้ถือว่ามาเล่าสู่กันฟังจากประสบการณ์ เป็นเรื่องโปรแกรมมิ่ง ถ้าไม่สนใจเรื่องโปรแกรมมิ่งก็ผ่านไปได้ครับ

PyQt5 กับลิขสิทธิ์แบบ GPL v3

ผมเขียนไพทอนด้วยการใช้ PyQt5 มาได้สักระยะเวลาหนึ่ง น่าจะสองปีกว่าได้ ยอมรับว่าชอบมากๆ ก็ไม่ได้ระแวดระวังเรื่องกฎหมายลิขสิทธิ์เท่าไหร่นัก ลิขสิทธิ์ของ PyQt5 เป็นแบบ GPL v3 ซึ่งสาระโดยรวมๆสามารถเอาไปใช้ได้สองกรณีคือ พัฒนาโปรแกรมแบบเปิดโค๊ด (open source) และแจกจ่ายฟรีพร้อมโค๊ด กรณีนี้ไม่จำเป็นต้องเสียเงินค่าลิขสิทธิ์ แต่ถ้าเป็นกรณีที่สองคือการพัฒนาโปรแกรมเพื่อการค้าและปิดโค๊ด จะต้องเสียค่าลิขสิทธิ์ประมาณ 500US$ ต่อปี ซึ่งไม่น่าจะมากมายนัก ผมเองเอา PyQt5 มาใช้และแจกจ่ายโปรแกรมของผมให้ฟรีก็จริงแต่ปิดโค๊ด ยังไม่พร้อมที่จะเปิดโค๊ดและจ่ายค่าลิขสิทธิ์เนื่องจากไม่ได้หารายได้จากโปรแกรมที่แจกจ่ายไป

ทางสองแพร่ง

ผมลองมองหาเครื่องมือพัฒนาตัวอื่นๆที่มีลิขสิทธิ์เอื้อแบบปิดโค๊ดแต่ยังสามารถใช้ได้ฟรี ที่อ่านๆมาแล้วอยู่ในใจคือ Kivy Platform

  • Kivy framework Platform รองรับภาษาไพทอน ข้อดีหลายอย่างคือ  cross platform  เขียนโปรแกรมครั้งเดียวสามารถนำไปคอมไพล์ บิวด์ได้บน Linux, Mac OS หรือสามารถพอร์ตลงไปหามือถือ Android, IOS ก็ได้ ที่สำคัญคือเป็นโครงการเปิดโค๊ด มีลิขสิทธิ์แบบ MIT License ซึ่งนอกจากจะเปิดโค๊ดแล้ว ยังอิสระเสรีมาก ซอฟแวร์ที่ใช้ลิขสิทธิ์แบบนี้ ผู้ใช้สามารถเอาไปทำอะไรก็ได้ตั้งแต่ก๊อปปี้ แก้ไข รวม แจกจ่ายหรือกระทั่งนำไปขายก็ได้ สุดท้ายถ้าใช้ Kivy พัฒนาก็ยังสามารถนำโปรแกรมไปทำการค้าได้
  • แต่ปัญหาของผมคือ โปรแกรมที่เขียนมีขนาดค่อนข้างใหญ่แล้ว การพอร์ตจากโค๊ดของ PyQt5 ไปยัง Kivy ไม่ใช่่จะทำได้ง่ายๆ เนื่องจาก Kivy มีรูปแบบ GUI ของตัวเอง ไม่ง่ายครับต้องใช้เวลามาก

PySide2 ผู้มาช่วยชีวิต

ในขณะที่กำลังจะจมน้ำอยู่นั้น นึกถึง PySide รุ่นแรกซึ่งรองรับและใช้ได้แค่ Qt4 ไม่ใช่ Qt5 ที่ผมตกร่องปล่องชิ้นไปแล้ว การจะ downgrade กลับหลังหันไปใช้ Qt4 ผ่าน PySide รุ่นหนึ่งไม่ได้ง่ายต้องรื้อโค๊ดพอสมควร เผลอๆอาจจะยากกว่าการกลับไปขอคืนดีกับแฟนเก่า 🙂 ส่วนเรื่องลิขสิทธิ์เท่าที่ทราบมาคือ PySide ใช้ลิขสิทธิ์แบบ LGPL v2.1 คือสามารถนำไปใช้พัฒนาโปรแกรมสำหรับปิดหรือเปิดโค๊ดได้ ทั้งแจกจ่ายฟรีและขายได้ เพียงแต่ไปเอาไลบรารีตัวไหนที่เป็น LPGL มาใช้จะต้องคงความเป็น LGPL ไว้คือต้องเปิดโค๊ตไลบรารีตัวนั้นไปให้ผู้ใช้ด้วย  ข่าวร้ายของ PySide รุ่นหนึ่งคือโครงการตาย ไม่ขยับมาแล้วสามปีกว่า ผมลองค้นเข้าไปลึกๆ ปรากฎว่าโครงการนี้ตอนแรกๆ ได้มีนักพัฒนาอิสระ fork โครงการมาบน Github กลุ่มเล็กๆต่อมาได้ย้ายและไปพัฒนาต่อเป็นทางการจาก Qt Company เจ้าของ Qt framework ตัวจริงเสียงจริง เหมือนฟ้ามาโปรด แต่ยังไม่แน่ใจว่าโค๊ดเดิม PyQt5 ของผมจะ compatible กับ PySide2 แค่ไหน

ในขณะที่ไม่รู้จะทำอะไรก็เลยไปดาวน์โหลดหยิบเอา PySide ตอนนี้เป็นรุ่น PySide2 ที่กำลังพัฒนาอยู่ เป็นไฟล์ wheel ครับที่ทีมงานได้คอมไพล์และบิวท์มาให้ลองใช้ก่อน รุ่นที่ผมหยิบมาใช้ในขณะนี้ บิวท์นานมาแล้วประมาณหกเดือนกว่า รุ่นล่ากว่านี้ยังไม่มี

PySide2 ใช้ลิขสิทธิ์แบบ LGPL v2 เหมือนกันกับ PySide รุ่นหนึ่ง เมื่อดาวนโหลดมาแล้วก็ติดตั้ง มาลองดูว่าพอไปได้ไหม ผมใช้เวลาว่างๆตอนเลิกงานลองไปหก เจ็ดวัน สำเร็จครับ ส่วนใหญ่ใช้ได้กับโค๊ดเดิม แค่ตอน import ไลบรารีเปลี่ยนแค่หัวจาก PyQt5 มาเป็น PySide2 มีส่วนนิดเดียวผมแก้ไขโค๊ดใหม่ให้เข้ากับ PySide2 แต่น้อยมาก และที่เจออีกหนักอีกหน่อยคือ ระบบรายงานผลการแสดงภาพบนจอมอนิเตอร์คือ Screen ยังไม่เสร็จ ทำให้ผมไม่สามารถตรวจได้ว่าผู้ใช้ใช้จอที่ resolution เท่าไหร่ มี dot pixel  ratio เท่าไหร่ อันนี้สำคัญเพราะว่าจอ HiDPI เช่นจอ 4K ทั้งหลาย ตอนนี้ใช้กันมากแล้ว เมื่อ PySide2 ไม่มีให้ ต้องไปหาโค๊ดมาช่วย ใช้ Windows API ไปพลางๆก่อน พอเสร็จเมื่อไหร่ ค่อยกลับมาใช้โค๊ดของ PySide2 ที่เสร็จแล้วต่อ

ผมพูดได้ว่าตอนนี้โปรแกรมของผม เกือบจะ 99.9% ใช้ของเดิม มาปรับแต่งเองเพียง 0.1% มันง่ายหรือเพราะโปรแกรมผมไม่ได้ใช้เขียนอะไรพิศดารหรือปล่าวเช่นระบบกราฟฟิคที่เลิศหรู แต่เอาละต้องขอบคุณทีมงาน PySide2 มา ณ ที่นี้ด้วยครับ

ข้อจำกัด PySide2 รุ่นพัฒนา

ข้อจำกัดของ PySide2 รุ่นพัฒนายังมีอยู่มากครับ บางครั้งผมรันโปรแกรมผ่านบรรทัดที่ผิด ไม่มีการนับแปดครับ โดนน๊อคกลางอากาศค้างไปดื้อๆ ก็น่าจะอีกสักพักครับ ที่ทีมงานของ Qt จะบิวท์มาให้ใช้กันใหม่ เพราะของเก่าผ่านมาหกเดือนกว่าแล้ว ตัวใหม่น่าจะเสถียรกว่า ดีกว่า

แปลงเป็นไฟล์ Execute ให้รันได้ นรกของโปรแกรมเมอร์ไพทอน

ปกติผมใช้ PyInstaller  สำหรับแปลงโค๊ดไพทอนเป็น execute file (exe) ที่สามารถนำไปรันได้ ทั้งที่ข้อดีของไพทอนคือเขียนง่าย อ่านง่าย ทรงพลัง และไลบรารีที่มีให้เลือกให้ใช้มากมายมหาศาล แต่เป็นที่รู้กันว่างานแปลงไฟล์จากโค๊ดไปเป็นไฟล์ exe ที่รันใช้งานได้ มันเป็นงานสุดหินสุดโหด ตอนผมเริ่มต้นใหม่ๆ ผมก็ไม่รู้ว่าไลบรารีตัวไหน ต้องการขนเอาไฟล์อะไรไปบ้างเพื่อให้สามารถรันได้ บางตัวขนกันไปเป็นโฟลเดอร์ คือแบบเรือพ่วงลากกันไปเป็นพวงๆ ตอนหลังมารู้ว่า มีไฟล์ hooks ที่ PyInstaller  อ่านมาเพื่อตัดสินใจว่าจะขนอะไรไปให้ โฟลเดอร์ไหน ก็ง่ายขึ้น ไฟล์ hooks ส่วนใหญ่มากับ PyInstaller  ถ้าไม่มีก็ไปค้นหาดาวน์โหลดมาได้

ตอนนี้ PySide2 ยังมีนักพัฒนาเอาไปใช้ในวงจำกัดอยู่ จึงไม่มีไฟล์ hooks ปล่อยมา นรกกลับมาเริ่มต้นกับผมอีกครั้ง ต้องลองทดสอบว่า PySide2 เอาอะไรไปใช้บ้าง ตอนนี้ขณะเขียนบทความนี้ ยังไม่สำเร็จครับ กำลังลองผิดลองถูก ไม่เป็นไรชีวิตย่อมมีหนทางเสมอ ผมใช้เวลาในวันอาทิตย์เกือบค่อนวันก็สำเร็จ สามารถนำไฟล์ exe ไปรันได้ตามปกติ สาเหตุที่ไม่ผ่านในตอนแรก ตัว PyInstaller มีปัญหากับโมดูลระบบชื่อ requests ต้องเอา requests รุ่นที่ไม่มีปัญหากันมาใส่แทน

เมื่อไฟล์ exe รันแล้วไม่ติดขัด ก็จะเป็นขั้นตอนต่อไปคือใช้โปรแกรมจำพวก installer มาใช้เช่น Inno Setup ขวัญใจมหาชนเจ้าประจำผมใช้อยู่ ขั้นตอนนี้ง่ายครับ เตรียมไฟล์ exe ให้พร้อม ไฟล์ไลบรารีทั้งหลาย โฟลเดอร์ที่จำเป็นต้องใช้ จากนั้นทำการ build ก็ได้โปรแกรมติดตั้ง ที่สามารถนำไปติดตั้งใช้งานได้

ดาวน์โหลด (Download)

ไปดาวน์โหลดโปรแกรมรุ่นที่บิวท์ด้วย PySide2  เป็นรุ่น V0.70 build 513 ได้แล้วที่หน้า Download ใครที่ใช้รุ่นก่อนหน้านี้ขอความกรุณาช่วย uninstall และมาดาวน์โหลดรุ่นใหม่ไปใช้ด้วย

python_2017-04-22_18-37-32
Surveyor Pocket Tools ในฉบับของ PySide2 เหมือนเดิมเพราะ engine คือตัว Qt framework เดียวกัน
python_2017-04-22_18-49-45

ตัวอย่างหน้าตาของ Transform Coordinates

ความเป็นมาของ Qt framework

เล่าเรื่อง PyQt5 vs. PySide2 ผู้อ่านบางท่านอาจจะงง ถีงที่มาที่ไป PyQt และ PySide คือเครื่องมือสำหรับพัฒนาโปรแกรมประยุกต์ ที่มีพื้นฐานมาจาก Qt framework ที่พัฒนาด้วย C++ ผู้สร้าง Qt คือบริษัท Trolltech บริษัทเล็กๆนอรเวย์ จากนั้นถูกซื้อไปในปี 2008 โดยโนเกีย (Nokia) ยักษ์ใหญ่ในตอนนั้นจากฟินแลนด์ (ย้อนหลังไป 15-25 ปีที่แล้ว คงไม่มีใครไม่รู้จักโนเกียเพราะเป็นเจ้าพ่อแห่งวงการมือถือ ยุคก่อน IPhone OS และ Android) ทำให้ลิขสิทธิ์ของ Qt ตกมาอยู่กับโนเกีย และโนเกียพยายามจะพัฒนาให้ Qt สามารถใช้กับมือถือได้ (แต่ก็ไม่ทัน ไอโอเอสและแอนดรอยด์ จนแพ้สงครามนี้ในที่สุด) ช่วงที่ Qt อยู่กับโนเกีย ในขณะนั้น Riverbank Computing บริษัทจากอังกฤษได้พัฒนา PyQt  แต่ตอนหลังมีปัญหากัน เพราะ Riverbank ไม่ยอมเปลี่่ยนลิขสิทธิ์ของ PyQt ที่ใช้แบบ GPL ส่วน Qt ใช้ลิขสิทธิ์แบบ LGPL ซึ่งยืดหยุ่นกว่า โนเกียก็ได้พัฒนา PySide ขึ้นมาเพื่อให้มีลิขสิทธิ์แบบเดียวกันกับ Qt แต่สถาณการณ์ของโนเกียตอนนั้นกำลังย่ำแย่มากๆ เพราะมือถือจากไอโอเอสของแอปเปิ้ลกับแอนด์ดรอยของกูเกิ้ลได้ครองตลาดเบ็ดเสร็จแล้ว

ในปี 2011 โนเกียขาย Qt ให้บริษัท Digia จากฟินแลนด์ หลังจากนั้นฉากสุดท้ายแล้วก็เป็นที่ทราบกันดีว่าโนเกียถูกซื้อเสนอซื้อโดยไมโครซอฟท์ในปี 2013 เฉพาะส่วนที่เกี่ยวกับมือถือสมาร์ทโฟนจบการดีลการซื้อขายในปี 2014 และจบตำนานโนเกียในที่สุด สะท้อนของสัจธรรมที่ว่าในโลกนี้ไม่มีอะไรเที่ยงแท้แน่นอน หลังจาก Digia ได้ Qt ไปในปี 2014 ได้ก่อตั้งบริษัท Qt Company และเป็นผู้พัฒนา Qt จนถึงปัจจุบัน และตามที่ผมกล่าวมาแล้ว Qt Company ได้ดึงโครงการ PySide มาสานต่อเป็นโครงการ PySide2 ซึ่งถ้าเปิดใช้เป็นทางการเมื่อไหร่ ผมก็เป็นคนหนึ่งที่ตั้งหน้าตั้งตารอคอย

โครงการในอนาคต

ก็เป็นโครงการที่วาดฝันครับ คือพอร์ตโปรแกรม Surveyor Pocket Tools ลงบนมือถือด้วย Kivy framework คงแยกแต่ละ tool ไปเป็นแต่ละ app ซึ่งก็มีฟรีบ้างขายบ้าง ก็เป็นเรื่องอนาคตไม่ได้ตั้งความหวังอะไรมากมาย ปัจจุบันคือพัฒนาและปรับปรุงโปรแกรมทั้งหลายบน Desktop ให้ใช้งานกันต่อไป และยังยืนยันว่าฟรีเหมือนเดิม และความคิดก็ยังเหมือนเดิมครับ “โลกนี้จะน่าอยู่ ถ้าทุกคนแบ่งปัน ถ้อยทีถ้อยอาศัยกัน

ทิ้งท้ายกันนิดหนึ่งจากหัวข้อ “ปลดพันธนาการ” ก็ดูจะโหดร้ายไป ตั้งให้น่าสนใจแค่นั้นครับ PyQt5 นั้นเป็นเครื่องมือพัฒนาโปรแกรมที่ดีมาก มี document ให้อ่านเยอะแยะ ติดขัดตรงไหน Stackoverflow ช่วยได้ สำหรับคนใช้เครื่องมือเพื่อการค้า ก็อุดหนุนซื้อกันไป แต่ถ้าพัฒนาโปรแกรมแบบเปิดโค๊ดก็ตัวนี้เลย ไม่ผิดหวัง สำหรับผมขอเลือก PySide2 ด้วยเหตุผลที่กล่าวมาข้างต้นทั้งหมด พบกันตอนต่อไปครับ

Surveyor Pocket Tools โปรแกรมรวมเครื่องมือฉบับกระเป๋าสำหรับช่างสำรวจ (แจกฟรี) – ตอนที่ 1

ความเป็นมา

  • ปกติผมเป็นคนชอบพกเครื่องมือสารพัดประโยชน์เช่นมีดพับในกระเป๋ากางเกง เพราะสะดวกรวมทุกสิ่งทุกอย่างทั้งไขควง มีด กรรไกร ตะไบ ที่เปิดขวด ยี่ห้อที่ชอบได้แก่  Leatherman, Victorinox เมื่อสองปีที่แล้วไปโมซัมบิค อาฟริกา ท่านศุลกากรที่นั่นเปิดกระเป๋าเดินทางเพื่อตรวจก่อนออกจากสนามบิน  แอบสอย Victorinox ของผมไปหน้าตาเฉย เสียดายครับเพราะใช้มาหลายสิบปี แต่ก็อโหสิกรรมให้ท่านผู้นั้นไปแล้ว โปรแกรมที่จะแนะนำก็คล้ายกับมีดพับสารพัดประโยชน์แบบเดียวกัน
  • ที่ใช้บ่อยสำหรับคนที่ทำงานสนามคือโปรแกรมแปลงพิกัดค่ากริดยูทีเอ็ม (UTM) แปลงไป  แลตติจูด/ลองจิจูด (Geographic)  หรือกลับกัน ส่วนใหญ่จะเปิดคำนวณผ่านออนไลน์ ได้ค่ามาแล้ว บางทีก็ copy มาวางที่่  google earth หรือ google map  เพื่อปักหมุดให้รู้ว่าจุดตัวนั้นอยู่ตรงไหน หลายขั้นตอนบางครั้งรู้สึกรำคาญ
  • ไปๆมาๆ เขียนใช้เองดีกว่า ตรงใจที่สุด ป้อนค่าพิกัดเสร็จก็คลิกแปลงพิกัดทันที เสร็จแล้วก็ปักหมุดลงใน google maps ทันที

spt_intro

  • สิ่งที่ตั้งใจไว้ก็คือจะเขียนโปรแกรมคำนวณย่อยเล็กๆเอามารวมกัน ที่ใช้บ่อยในงานสนาม ตอนนี้โปรแกรมยังเป็นแค่รุ่น 0.35 รุ่นทดลองใช้ ยังมีอีกหลายอย่างที่จะใส่เพิ่มเข้ามาอีกในรุ่นหน้า ตอนนี้เอาแค่นี้ก่อน

ใครคือผู้ใช้งานโปรแกรม

  • ดูจากชื่อโปรแกรมเหมือนกับเอาไว้ให้ ช่างสำรวจใช้อย่างเดียว แต่ไม่ใช่ครับ ใครก็เอาไปใช้ได้ เพราะโปรแกรมผมได้พยายามออกแบบให้ใช้งานได้ง่ายๆ

ดาวน์โหลดและติดตั้ง

  • ดูลิ๊งค์ด้านขวามือ “ดาวน์โหลด (Download)” มองหาโปรแกรม Surveyor Pocket Tools จะมีรุ่นและ build ปกติดูที่ build เป็นหลัก เนื่องจากโปรแกรมนี้อยู่ในระหว่างพัฒนาตัวเลข build จะเพิ่มขึ้นเรื่อยๆ

 

  • คลิกเพื่อดาวน์โหลด เสร็จแล้วแตกไฟล์ zip ออกมาจะได้ไฟล์ .exe พร้อมทำการติดตั้ง เมื่อติดตั้งแล้วจะเห็นไอคอนรูปกล้องเซอร์เวย์

icon_sptเริ่มใช้งาน

  • เมื่อเปิดโปรแกรมมาจะเห็นหน้าตาดังรูปด้านล่าง แต่ละไอคอนจะมีโปรแกรมย่อยๆให้ใช้งาน

intro-surveyor-pokcet-tools

  • มาดูกันว่าแต่ละโปรแกรมย่อยทำอะไรได้บ้าง เรียงเลยครับจากบนลงล่าง

โปรแกรมแปลงพิกัดกริดยูทีเอ็ม (UTM – Geo Converter)

  • สำหรับแปลงพิกัดกริด UTM ได้แก่ค่า Northing(N), Easting(E) ไปค่าพิกัดภูมิศาสตร์ Latitude, Longitude  ในที่นี้จะแปลงค่าพิกัดบนรูปทรงรี WGS84 เท่านั้น แต่สามารถแปลงจากพิกัดภูมิศาสตร์ไปค่าพิกัดกริดยูทีเอ็ม เมื่อดับเบิ้ลคลิกที่ไอคอนจะเห็นหน้าต่างโปรแกรมแปลงพิกัดเล็กๆแสดงออกมาด้านขวา มีช่องพิกัดให้กรอกช่องด้านซ้ายหรือด้านขวา

spt_utm2geo-01

  • ช่องด้านซ้ายมือผู้อ่านจะเป็นช่องให้ป้อนค่าพิกัดกริดยูทีเอ็ม มีช่อง Point Name คือป้อนชื่อจุด และเรียงลงมาคือป้อนค่า N, E สุดท้ายต้องบอกโซนด้วยครับเพราะระบบกริดยูทีเอ็มมีหลายโซนทั้งโลก ค่าพิกัดอาจซ้ำกันถ้าไม่ระบุโซนจะไม่รู้เลยว่าอยู่ที่ไหน
  • ผมจะลองแปลงพิกัดจากกริดยูทีเอ็มไปหาค่าพิกัดภูมิศาสตร์ โดยที่จุดหรือหมุดมีค่าพิกัดดังนี้ ชื่อหมุด “006 -031” ค่า N = 1598702.276 ค่า E=707105.198 Zone No. = UTM Zone 47N ดังรูปด้านล่าง

spt_utm2geo-03

  • จากนั้นคลิกที่ไอคอนรูปลูกศรเพื่อแปลงค่าพิกัดจะเห็นค่าพิกัดแปลงไปเป็นค่าพิกัดภูมิศาสตร์ดังรูปด้านบน ล่างขวาจะเห็นรูปแบบที่จะแสดงมุมได้สามอย่างคือแบบทศนิยม แบบแยกองศา ลิปดา ฟิลิปดา (DD°MM’SS.SSSS”) และแบบที่สามคือองศา ทศนิยมของลิปดา (DD°MM.MMMM’)

spt_utm2geo-04

  • กรณีค่าแลตติจูดเป็นค่าบวกคืออยูเหนือเส้นศูนย์สูตร จะมีคำว่า “N” เป็น suffix ไปแปะไว้ด้านหลัง ถ้าอยู่ต่ำกว่าเส้นศูนย์สูตรจะใข้คำว่า “S”  เช่นเดียวกันกับค่าลองจิจูดถ้าค่าเป็นบวกจะใช้คำว่า “E” ย่อมาจาก East แปะด้านหลัง ถ้าค่าเป็นลบจะใช้คำ “W” ย่อมาจาก West  แปะไว้ด้านหลัง
  • ลองเปลี่ยนรูปแบบมุมเป็น DD°MM.MMMM’

spt_utm2geo-05

ปักหมุดบน Google Maps

  •   ต่อไปจะปักหมุดบน google maps กระบวนการนี้ต้องออนไลน์นะครับ  คลิกที่ไอคอนด้านล่างตามรูป โปรแกรมจะเรียก  web browser ที่ใช้งานมาแสดง

spt_utm2geo-06

  • รูปด้านล่างแสดง google maps พร้อมหมุดที่ปักแล้ว สะดวกดีครับ ดูตรง address bar จะเห็นว่าเรียกไฟล์จากเครื่องที่ติดตั้งโปรแกรมใช้งานอยู่ดูเหมือนจะออฟไลน์ แต่ไม่ใช่ครับ ยังออนไลน์สามารถซูมเข้าออกได้ปกติ

gm_006031

ปักหมุดบน Google Earth

  • มาลองปักหมุดบน google earth ดูกัน เครื่องที่ใช้ต้องติดตั้ง google earth ด้วยนะครับ คลิกที่ไอคอนดังรูปด้านล่าง

spt_utm2geo-07

  • โปรแกรมจะถามชื่อไฟล์เพื่อจัดเก็บไว้ เผื่อกรณีต้องการใช้ภายหลัง แล้วจะสวิชต์เข้าโปรแกรม google earth ทันที จะเห็นหมุดสีเหลืองปักกลางรูป

googleearth01

  • มาลองแปลงค่าพิกัดภูมิศาสตร์ไปเป็นค่าพิกัดกริดยูทีเอ็ม ทีนี้จะลองรูปแบบมุมเป็นทศนิยมดีกรี ผมป้อนค่าพิกัดที่อยู่ที่โมซัมบิค อาฟริกาที่เคยไปทำงานอยู่ดังรูป คลิกไอคอนเพื่อแปลงพิกัด

spt_utm2geo-08

  • จะเห็นว่าโปรแกรมจะแปลงพิกัดให้พร้อมทั้งคำนวณ UTM Zone ให้ได้ถูกต้อง จุดนี้อยู่ในประเทศโมซัมบิค ทวีปอาฟริกาอยู่ใต้เส้นศูนย์สูตรและยูทีเอ็มโซนคือ  UTM Zone 37S

spt_utm2geo-10

  • ลองปักหมุดบน google earth

googleearth02

จัดเก็บข้อมูลจุดที่ใช้งานเข้าฐานข้อมูล

  • เนื่องจากเป็นโปรแกรมขนาดเล็กๆ ไม่ได้ตั้งใจจะออกแบบให้อ่านเขียนไฟล์ข้อมูลได้เป็นเรื่องเป็นราว ผมจึงทำฐานข้อมูลขนาดเล็กๆ สามารถเก็บจุดที่ใช้งานได้ เผื่อต้องการเรียกนำมาใช้งานได้อีก ก็สามารถลากจากตารางฐานข้อมูลเข้าช่องป้อนตัวเลข
  • ต้องการจัดเก็บก็คลิกที่ไอคอนรูปหมุดที่มีเครื่องหมายบวก โปรแกรมจะถามคอนเฟิร์มอีกทีตอบ “OK”

spt_utm2geo-11

  • จากนั้นโปรแกรมจะเปิดตารางข้อมูล ตรวจสอบว่าข้อมูลเข้าไปเรียบร้อยแล้วดังรูป ส่วนจุดอื่นๆที่ปรากฎในตารางผมได้เพิ่มมาก่อนหน้านี้แล้ว

spt_utm2geo-12

วิธีใช้งานตารางข้อมูลกริดยูทีเอ็ม

  •  ในตอนนี้ผมจัดทำตาราง (Table) เก็บค่าพิกัดที่เป็นกริดแยกกับตารางที่เก็บค่าพิกัดภูมิศาสตร์ แต่อยู่ในฐานข้อมูลเดียวกัน มาดูวิธีใช้งาน คลิกที่ไอคอนดังรูป

spt_utm2geo_opentable-01

  • จะเห็นตารางข้อมูลขึ้นมา ตอนนี้จะใช้งาน ต้องการค่าพิกัดตัวไหนก็ใช้เมาส์เลื่อนสกรอลบาร์ไปได้ ถ้าคลิกที่ปุ่ม “First” จะไปที่ตัวแรก ถ้าคลิกที่ “Last”  จะไปที่ตัวสุดท้าย ถ้าต้องการลบก็คลิกที่ “Delete”
  • ตอนนี้สมมุติว่าผมต้องการค่าพิกัด “AC-01-GPS” มาใช้งาน คลิกเมาส์ที่ค่าพิกัดตัวนี้แล้วลากไปวางที่ช่อง UTM ดังรูป

spt_utm2geo_draganddrop-01

  • จะได้ค่าพิกัดจากตารางข้อมูลมาปรากฎในช่อง UTM ดังรูป แปลงพิกัดก็คลิกที่รูปลูกศรขวาได้เลย

spt_utm2geo_draganddrop-02

  • ลองลากค่าพิกัดกริดยูทีเอ็มจากตารางข้อมูลไปที่ช่อง Geographic เพื่อแปลงพิกัด

spt_utm2geo_draganddrop-03

  • โปรแกรมจะเตือนว่าผู้ใช้ต้องการแปลงพิกัด

spt_utm2geo_draganddrop-04

  • คลิก “OK” จะได้ผลลัพธ์

spt_utm2geo_draganddrop-05

วิธีการใช้ตารางข้อมูลค่าพิกัดภูมิศาสตร์

  • จากที่กล่าวไปแล้วตารางข้อมูล (table) มีสองแบบ ที่นี้มาลองใช้ตารางค่าพิกัดภูมิศาสตร์ดูบ้าง ตอนนี้จะจัดเก็บจุด “006-053” เข้าตารางข้อมูล คลิกที่ไอคอนรูปหมุดดังรูป

spt_ge2utm_draganddrop-02

  • จะเห็นตารางข้อมูลขึ้นมา และค่าพิกัดถูกจัดเก็บเข้าไปเรียบร้อยดังรูปด้านล่าง จะเห็นตารางข้อมูลสองตารางที่ลากขยับหน้าต่างเรียงกันใหม่

spt_geo_table

  • ลองคลิกไอคอนลูกศรไปทางซ้ายเพื่อแปลงพิกัดจากพิกัดภูมิศาสตร์ไปหาพิกัดกริดยูทีเอ็ม จะได้ค่าเดิมก่อนหน้านี้

โปรแกรมแปลงพิกัดข้ามพื้นหลักฐาน (Transform Coordinates)

  • โปรแกรมที่แล้วสำหรับแปลงพิกัดบนพื้นหลักฐาน WGS84 เท่านั้น แต่ถ้าต้องการแปลงข้ามพื้นหลักฐาน (datum) จะทำยังไง โปรแกรมที่สองที่ผมเขียนขึ้นมาเพื่อตอบโจทย์นี้ แต่เนื่องจาก datum และเส้นโครงแผนที่ (Map Projection) ในโลกนี้มันมีมากมายมหาศาลต่างประเทศต่างใช้หลากหลายกันไป สำหรับประเทศไทยมีสองพื้นหลักฐานที่ใช้กันคือ WGS84 และ Indian 1975 เส้นโครงแผนที่ของบ้านเราใช้ UTM
  • จากโปรแกรมหลักเมื่อคลิกโปรแกรมที่สอง จะเห็นหน้าตาแบบนี้  ด้านซ้ายเป็นระบบพิกัด ที่สามารถเลือกพิ้นหลักฐานและเส้นโครงแผนที่ได้ ด้านขวาก็เช่นเดียวกันสามารถเลือกได้อิสระ

surveyor-pocket-tools_2016-10-27_07-30-03

การป้อนรูปแบบของมุม

  • การป้อนมุมสำหรับโปรแกรมชุดนี้ ค่อนข้างต้องเป๊ะครับ ผมจะอธิบายให้พอเข้าใจ ถึงที่มาที่ไป ในทางโปรแกรมมิ่งผมใช้ที่เขาเรียกว่า Regular Express คอยสอดส่องว่าผู้ใช้กำลังป้อนอะไรเข้ามา ตรงไหนเป็นตัวเลข 0-9 ตัวไหนเป็นสัญลักษณ์เช่นองศา ° ลิปดา ‘ ฟิลิปดา ” หรือตรงไหนเป็นตัวอักษร d, N, S, E, W และต้องไม่มีช่องว่าง
  • รูปแบบ DD MM SS.SSSS เช่น 14°36’44.21988″N ต้องไม่มีช่องว่างและต้องป้อนสัญลักษณ์ให้ครบทั้งสาม แต่สัญลักษณ์องศาให้ใช้ตัว d แทนได้เช่น 14d36’44.21988″N 
  • โปรแกรมแก้ไขเรื่องป้อนมุม ดูที่  build 375 ขึ้นไป การป้อนมุมมีช่องว่างได้ครับ 14°36’44.21988″N สามารถป้อนแบบนี้ได้ 14d 36′ 44.21988″ N ถ้าป้อนไปแล้วผิดให้เอาเคอร์เซอร์มาไว้ที่ท้ายสุดแล้วกด backspace ไปเรื่อยๆ ดูโปรแกรมว่า build เท่าไหร่เปิดดูที่ About ครับ

surveyor-pocket-tools_2016-11-14_13-58-54

  • ก่อนจะมาว่ากันต่อคงต้องยกยอดไปตอนที่ 2 ครับ

เครดิตและโปรแกรมมิ่ง

  • โปรแกรมนี้สำเร็จมาใช้งานไม่ได้เลย ถ้าไม่มีไลบรารีภาษาไพธอน (Python) ที่แจกให้ใช้ฟรีเหล่านี้ ผมขอยกเครดิตความดีให้
    • PyProj โดย Jeff Whitaker ที่พัฒนามาจาก Proj.4 ช่วยให้งานคำนวณแปลงพิกัดข้ามพื้นหลักฐานอันุยุ่งยากกลายเป็นเรื่องง่าย
    • GeographicLib โดย Charles F. F. Karney ช่วยในการคำนวณระยะทางสั้นที่สุดบนอิลลิปซอยด์โดยวิธี Geodesic distance
    • SimpleKML โดย Kyle Lancaster ช่วยในการปักหมุดลงใน Google Earth
    • gmplot โดย Michael Woods ช่วยในการปักหมุดลง Google Maps
    • EGM96 เป็นโค้ดภาษาซี ผมแปลงเป็นภาษาไพธอนเพื่อคำนวณ Geoid Separation ด้วยโมเดล EGM96
    • EGM2008  เป็นโค้ดภาษาซี โค้ดดั้งเดิมอยู่ในโครงการ Geotrans เช่นกันผมแปลงเป็นภาษาไพธอน เพื่อนำมาคำนวณหา Geoid Separation ด้วยโมเดล EGM2008 แบบ 2.5′ x 2.5′
  • โปรแกรมพัฒนาด้วย Python 3.5 กราฟฟิค User Interface ด้วย PyQt5 ตอนนี้รุ่น 5.5.7 สภาพแวดล้อมในการพัฒนาโปรแกรมไพธอนใช้ Eric Python IDE ดีมากจนไม่เคยคิดจะกลับไปใช้ตัวอื่นอีกเลย
  • พบกันตอนใหม่ครับ

สิ้นสุดการรอคอย XSection Plot รุ่น 4.10 มาแล้ว ตอนที่ 3 (จบ)

10.การจัดเรียงรูปตัดบนกระดาษเขียนแบบ (Page Layout)

  • การจัดเรียงรูปตัด เลือกทูลบาร์และคลิกที่ “Page layout” ได้ดังรูป

page_layout_toolbar

  • ความยาวของรูปตัดแต่ละรูปตัด ความกว้างในแนวดิ่ง มาตราส่วน จะมีผลต่อขนาดของรูปตัดแต่ละรูป ที่่จะวางบนกระดาษเขียนแบบ  การจัดเรียงรูปตัดให้เหมาะสม ผู้ใช้สามารถเลือกจำนวนรูปตัดในแนวดิ่ง จำนวนรูปตัดในแนวนอน ลองตั้งค่าตามรูปด้านล่าง เนื่องจากข้อมูลที่เรากำลังทดลองใช้งานอยู่มี 4 รูปตัดและความกว้างของรูปตัดประมาณ 1300 เมตร จึงยาวจรดกระดาษซ้ายขวา จำนวนรูปตัดในแนวดิ่งจึงเลือกเป็น 1 ส่วนจำนวนรูปตัดในแนวนอนเลือกเป็น 4

page_layout_1

  • มาดูตั้งค่าให้กริดทางราบ จัดระยะราบจาก CL (ค่าที่ X = 0) ไปด้านซ้าย ตั้งไว้ 700 เมตร เผื่อยาวเกินกว่าข้อมูลไว้นิดหนึ่งไม่ให้รูปตัดไปชน ด้านขวาตั้งไว้ 650 เมตร ส่วนระยะราบของกริดแต่ละช่อง(grid spacing) เลือกไว้ 10 เมตร

page_layout_2

  • มาดูการตั้งกริดทางดิ่ง ผมตั้งไว้ 21 เมตร ระยะของช่องกริดทางดิ่ง (มาตราส่วน 1:200) 1 เมตร ดูรูปกราฟฟิคที่ลากลูกศรเชื่อมโยงด้านล่างเพื่อความเข้าใจ และตั้งระยะของช่องกริดทางดิ่ง 1 เมตร

page_layout_3

  • เวลาเปลี่ยนค่า ต้องกปุ่ม “Apply” เพื่อโปรแกรมจะได้จัดวางรูปตัดให้ใหม่ ถ้ารูปตัดทับซ้อนกันโปรแกรมจะเตือนให้ทราบ ผมเตรียมไอคอนบนทูลบาร์ด้านบน เพื่อ  copy ค่าพิกัดของรูปตัดเมื่อเทียบกับค่าพิกัดของกระดาษ มุมล่างซ้าย x,y = 0,0 หน่วยเป็น ซม.ครับ เมื่อคลิกที่ไอคอน โปรแกรมจะคัดลอกค่าพิกัดลงไปตารางข้อมูล จากนั้นคลิก “OK” เพื่อปิดหน้าต่างไดอะล็อก

page_layout_coors

  • บันทึกไฟล์ข้อมูลลงดิสค์ ให้เรียบร้อย

11.แสดงรูปตัดตามขวาง (Section Viewer)

  • ในที่สุดก็มาดูรูปตัดที่เราตั้งค่าไว้ ว่าตรงตามที่ต้องการหรือไม่ ที่ทูลบาร์ คลิก “Section Viewer”

section_viewer_toolbar

  • จะเห็นรูปตัดตามขวางแสดงผลมาให้ดูแต่ละ section ซึ่งจะเริ่มจาก section แรกก่อน ดังรูปด้านล่าง การขยาย ย่อ แผนที่ทำได้โดยการ scroll เมาส์ปุ่มกลางครับง่ายๆ ส่วนการเลื่อนแผนที่คลิกกดปุ่มกลางของเมาส์แล้วลาก

section_viewer_elements

  • ค่าที่เราตั้งค่าสำหรับรูปตัดไว้ก่อนหน้านี้ เช่นค่าระดับกริดเส้นบน (Top grid elevation) หรือไม่รูปแบบการเขียนตัวเลข จะส่งผลต่อรูปตัด พิจารณาดูรูปด้านล่างที่แสดงเส้นลูกศรเชื่อมโยง

section_viewer_shown_topgrid_int

  • ส่วนค่าอื่นๆเช่นตั้งค่าให้คำนวณหาพื้นที่ดินตัด (cut) หรือดินถม (fill) หรือหาจุดตัดระหว่าง typical  และเส้นดินเดิม

section_viewer_shown_calcarea_interval

  • สังเกตรูปตัดด้านบนจะเห็นว่าตัวรูปตัดตกจากเส้นกริดลงมาด้านล่างเนื่องจากค่าระดับกริดเส้นบนใส่ค่ามากไป จากที่โปรแกรมคำนวณมาให้ ลองกลับไปตั้งค่าใหม่จาก 6 เมตร เป็น 4 เมตร จะเห็นว่ารูปตัดเลื่อนขึ้นไปอยู่ในกริด แต่เส้น existing ground ล้นกริดด้านบนไปแทน ซึ่งสามารถ trim ออกได้ในโปรแกรมด้าน CAD ในภายหลัง

section_viewer_shown_new_topgrid_elev

  • นอกจากนี้เมื่อเลื่อนเมาส์ไปยังพื้นที่ดินตัดหรือดินถม โปรแกรมจะแสดงผล interactive ตรงพื้นที่รูปตัดจะสว่างขึ้นช่วยให้ผู้ใช้เห็นพื้นที่ได้ชัดเจน

section_viewer_interactive

  • ลองคลิกที่ลูกศรเพื่อขยับไปดูรูปตัดถัดไป เสร็จแล้วจะออกจากการแสดงผลก็คลิกที่ไอคอน รูปประตู

section_viewer_next_prev12.บันทึกไฟล์ Autocad DXF (Save to DXF)

  • เมื่อตั้งค่ารูปตัดทั้งหลายจนเป็นที่พอใจ กระบวนการสุดท้ายที่ต้องการคือ แผนที่แสดงรูปตัดตามขวาง ที่สามารถส่งงานกันได้ ซึ่งจะใช้โปรแกรมอะไรด้าน CAD ความสามารถโปรแกรมเหล่านี้คือสามารถอ่านเขียนไฟล์ฟอร์แม็ต dxf ได้เพราะเจ้าของคือ Autodesk เปิดเป็นสาธารณะ ที่ทูลบาร์เลือกคลิก “Save to DXF

save_to_dxf_toolbar

  • โปรแกรมจะแสดงไดอะล็อก คลิกที่รูปไอคอนรูปดิสเก็ตเพื่อเลือกโฟลเดอร์ที่จะจัดเก็บตลอดทั้งป้อนชื่อไฟล์ทึ่จะบันทึกเป็น dxf ดังรูป

save_to_dxf_inputfile

  • เมื่อเสร็จสิ้นโปรแกรมจะขึ้น message box ว่าจัดเก็บเรียบร้อย คลิก “OK” และ “Close” เพื่อปิด

save_to_dxf_ready

13.เปิดไฟล์ด้วยโปรแกรมด้าน CAD

  • โปรแกรมด้าน CAD ขนาดเล็กๆ เขียนแบบ 2มิติ ใช้งานได้ฟรีขอแนะนำ Draftsight ดาวน์โหลด ติดตั้งแล้วลงทะเบียนก็พร้อมใช้งาน ใช้งานง่ายสะดวกครับ (ยังมีรุ่นเสียตังค์ซื้อด้วย ถ้าต้องการ) ลองเปิดไฟล์ dxf ที่บันทึกเก็บไว้จะเห็นผลงานออกมาดังรูป

draftsight2d-first

  • จะเห็นว่าตัว hatch สำหรับพื้นที่ดินตัด ดินถม ทึบสนิท ทั้งๆที่ผมตั้ง scale ของแฮ็ทช์ไว้ 1.0 ปัญหาไม่ใช่ของ Draftsight แต่มาจากไฟล์ dxf ซึ่งผมคิดว่าน่าจะเป็นไลบรารีเขียน dxf ที่ผมใช้งานอยู่คือ ezdxf แต่ไม่เป็นไรวิธีแก้กลับคือไปคลิกเลือก hatch ทุก section ที่ property มองหา scale เจอแล้วจะเห็นตัวเลข 1.000 ป้อนค่าใหม่เป็น 10.000 (หรืออะไรก็ได้) จากนั้นป้อนกลับไปค่าเดิมอีกครั้งเป็น 1.000 เหมือนเดิม จะเห็น hatch เปลี่ยนเป็นโปร่งขึ้น
  • ปัญหา hatch นี้เปิดใน Autocad ก็เป็นครับ

 

draftsight2d-hatch_ok

  • ผมลองเปิดข้อมูลเก่าที่ Titleblock เป็นภาษาไทย ลองดูก็ใช้ได้บน Draftsight สำหรับ Draftsight แบบสองมิติ อ่านดูในเน็ตเห็นว่ารุ่นนี้ฟรี (แต่ต้องลงทะเบียน ซึ่งง่ายๆ ถามไม่กี่คำถาม) แต่ก็ไม่แน่ใจว่าฟรีไปตลอดหรือไม่ ซึ่งก็น่าใช้งานมากครับถ้าฟรีตลอด สำหรับท่านที่ใช้งานแคด ที่ไม่ได้มีงานสามมิติเข้ามาเกี่ยวข้อง ดีกว่าไปละเมิดลิขสิทธิ์

draftsight2d_thai_ok

สรุป

  • โปรแกรมนี้ใช้เวลาพัฒนาประมาณ 2 ปีกว่าๆ แต่ไม่ได้ต่อเนื่องนะครับ เนื่องจากเขียนเป็นงานอดิเรก ไม่มีความกดดันอะไร ถือว่าเป็นการศึกษาด้านโปรแกรมมิ่งในตัวโดยเฉพาะ python เขียนสนุก กระชับ ทรงพลัง มีแง่บางอย่างที่นึกไม่ถึงว่าภาษาสามารถทำได้ และที่สำคัญมีไลบรารีหรือโมดูลให้ใช้มากมาย ส่วนใหญ่ฟรี เมื่อมารวมกับ Qt framework ที่เก่งอยู่แล้ว โปรแกรมที่เขียนออกมาแล้วจึงดูเป็นมืออาชีพพอสมควร แต่ก็แจ้งเตือนก่อนว่าโปรแกรม XSection Plot รุ่นนี้ยังมีบั๊กอยู่บ้าง ถ้าพบก็แจ้งมาได้ที่ blog นี้
  • เกือบลืมบอกไป โปรแกรมนี้สนับสนุนจอ 4K เรียบร้อยแล้ว ไม่มีปัญหาเรื่องไอคอนเล็ก ตัวหนังสือเล็ก

xsectionplot_about

  • ก่อนจากกันก็ขอบอกว่าโปรแกรมทุกโปรแกรมที่ผมพัฒนาขึ้นมายังคง concept เหมือนเดิมคือให้ใช้ฟรี และสำหรับช่างสำรวจและช่างโยธา เหมือนเดิมครับ

สิ้นสุดการรอคอย XSection Plot รุ่น 4.10 มาแล้ว ตอนที่ 2

6.เปิดไฟล์ข้อมูล (Open file)

  • ขั้นตอนต่อไปจะมาเปิดไฟล์ข้อมูล เริ่มจากไฟล์ Typical section ที่จัดเก็บไว้ใน Documents ขั้นตอนตามรูปด้านล่าง ขั้นตอนที่ 3 เลือกประเภทของไฟล์เป็น .txml โปรแกรมจะอ่านข้อมูล และแสดงผลบนตารางในแท็บ “Typical section”

open_typical_section_file

  • ต่อไปเช่นเดียวกันเปิดไฟล์ Existing Ground section

open_ground_section_file

7.ป้อนข้อมูลโครงการ

  • ตอนนี้ข้อมูลของเราที่ได้จาก import  จาก text file มีแต่รูปตัดในตารางเท่านั้น เราจะทำการบันทึกข้อมูลโครงการของงาน นอกจากบันทึกเพื่อจำแล้วยังสามารถนำไปเขียนเป็น Titleblock ใน Autocad คลิกที่ Toolbar ดังรูปด้านล่างedit_titleblock
  • จะเห็นหน้าต่างไดอะล็อกให้ป้อนข้อมูล ถ้ามีข้อมูลอะไรให้ลบออกหมด เนื่องจากโปรแกรมจะบันทึกสถานะครั้งสุดท้ายที่ใช้เอาไว้set_project_info_procedure
  • ป้อนข้อมูลโครงการเข้าไป (ข้อมูลมาจากการสำรวจจริง แต่ชื่อโครงการ บริษัท ชื่อคน สมมุติหมด)

set_project_info_filled

เลือกภาษาให้  Titleblock

  • ที่ผมเตรียมไว้ให้มีภาษาไทยกับอังกฤษเท่านั้น ถ้าต้องการใช้ภาษาไทยก็คลิกตามรูป

set_project_info_locale

8.ตั้งขนาดกระดาษสำหรับเขียนแบบ (Page Setup)

  • สำหรับงานเขียนแบบใน Autocad แล้วขนาดกระดาษที่เราใช้มักจะเป็นขนาดกระดาษมาตรฐานได้แก่ A4, A3, A2, A1 และ A0 ขึ้นอยู่กับขนาด printer หรือ plotter ที่ใช้ด้วย ที่ทูลบาร์หลักคลิกที่ “Page Setup”

page_setup_toolbar

  • จะมีหน้าต่างไดอะล๊อกให้เลือกตั้งขนาดกระดาษ (ขนาดกระดาษกำหนดโดยผู้ใช้  Custom Paper Size โปรแกรมตอนนี้ยังไม่ support) เลือก A1 ดังรูป

page_setup

9.ตั้งค่ารูปตัด (Cross-section settings)

  • รูปแบบรูปตัดเช่นการเลือกมาตราส่วนทางราบและทางดิ่งให้ตรงกับความต้องการ การจัดรูปแบบตัวอักษร ค่า x,y (offset/elevation) หรือการตั้งจำนวนทศนิยม สามารถตั้งค่าได้ที่นี่ จากทูลบาร์หลักเลือก “Xsection Settings” ดังรูป

section_settings_toolbarตั้งค่าทั่วไป

  • จะเห็นหน้าต่างไดอะล็อก มีแท็บทั้งหมด 4 แท็บ แยกค่าที่จะตั้งเป็นสัดส่วน มาดูหน้าแรกสำหรับตั้งค่ารูปตัดทั่วๆไป

section_settings_page1_desc

  • ข้อสังเกตรูปตัดจะเห็นว่าใช้มาตราส่วนทางราบ 1 : 2000 คิดง่ายๆ ในแผนที่วัดได้ 1 มม. = ระยะจริงในสนามได้ 2 เมตร หรือในแผนที่วัดได 1 ซม. เท่ากับระยะจริงในสนาม 20 เมตร ขนาดกระดาษ A1 594 x 871 มม. แต่ขนาดที่ปริ๊นท์ได้เท่ากับ 517 x 817 มม. ถ้าวางกระดาษในแนวนอน (landscape) ความยาวคร่าวๆคือ 800 มม. สามารถเขียนแบบรูปตัดตามขวางได้เท่ากับระยะในสนาม = 2 x 800 เมตร = 1600 เมตร
  • ถ้ากลับไปดูข้อมูลในตารางจะเห็นว่าจากซ้ายไปขวาประมาณ 1300 เมตร ซึ่งกระดาษ A1 และมาตราส่วนนี้สามารถเขียนแบบได้โดยที่ไม่ล้นกระดาษ

ตั้งค่ารูปแบบตารางแสดงตัวเลข offset/elevation

  • จากหน้าต่างไดอะล็อก คลิกที่แท็บที่ 2 เลือกรูปแบบแสดงตัวเลข  offset/elevation  จาก 3 รูปแบบที่มีรูปตัวอย่างแสดงไว้ให้เห็นง่ายๆ ที่นี้ผมเลือก  Type 3 ดังรูปด้านล่าง

section_settings_page2_desc

ตั้งค่าตัวหนังสือคำหน้า/ตามหลัง ค่าระดับ datum

  •  ที่หน้าต่างไดอะล็อกคลิกที่แท็บตัวที่ 3 “Grid datum postfix/prefix” เลือกแบบที่สาม เขียนตัวหนังสือ CD (Chard Datum)  ตามหลังค่าระดับ 0.0  บางครั้งงานทางทะเลจะระบุว่าค่า 0.0 เมตร อิงอยู่กับอะไรเช่นน้ำทะเลปานกลาง (MSL) หรือ Chart Datum (CD)

section_settings_page3_desc

 ตั้งค่าระดับเส้นกริด

  • เป็นการตั้งค่าระดับเส้นกริดตัวบนสุด ปรับได้ตามต้องการเพื่อให้รูปตัดอยู่กลางๆรูปตัด จะได้ดูง่าย ไม่อึดอัด

section_settings_page4_desc

  • แต่ก็สามารถเลือกตั้งได้อัตโนมัติโดยโปรแกรม ดูรูปด้านล่างประกอบ เมื่อเสร็จสิ้นการตั้งค่ารูปตัด สามารถคลิกปุ่ม “OK” เพื่อออกจากไดอะล็อก

section_settings_page4_auto

  • ต่อไปบันทึกข้อมูลลงไฟล์ ที่เราแก้ไขตั้งค่ารูปตัด โปรแกรมจะเก็บค่าทั้งหลายเอาไว้ในไฟล์ Existing ground section รวมทั้งข้อมูล offset/elevation ในตารางทุก  section
  • ก่อนจะทำอะไรต่อ ให้คลิกรูปดิสค์เก็ตที่ทูลบาร์ เพื่อบันทึกข้อมูลลงดิสค์ โลกสวยด้วยมือเราครับ

เปิดดูไฟล์ข้อมูล

  • ลองเปิดไฟล์ข้อมูลด้วยโปรแกรมเช่น  Notepad, Notepad++  จะเห็นข้อมูลดังข้างล่าง (คลิกที่สี่เหลี่ยมลิ๊งค์ด้านล่าง เพื่อขยายออกมา)

[sourcecode language=”xml” collapse=”true” title=”ไฟล์ข้อมูล Existing ground section”]
<root version=”1.0″>
<!–Generated by XSection Plot–>
<!–This data file is Existing ground section–>
<Header>
<AppName>XSection Plot</AppName>
<Developer>Prajuab Riabroy</Developer>
<Version>4.1.512</Version>
<SectionType>Ground</SectionType>
<DateCreated>2016-08-15 21:09:30.751163</DateCreated>
</Header>
<ProjectInfo>
<ProjectName>Macuzy International Port</ProjectName>
<ClientName>MX Port Authority</ClientName>
<ContractorName>East West Construction &amp; Engineering</ContractorName>
<DrawingTitle>X-Section of Approach Channel</DrawingTitle>
<DrawnName>Sergio Motezia</DrawnName>
<ApprovedName>Prati Wisas</ApprovedName>
<ClientApprovedName>Mario P. Tenes</ClientApprovedName>
<CheckedName>Antonio Molibzo</CheckedName>
<SurveyorName>Suras Malcom</SurveyorName>
<SurveyedDate>1-7/03/2016</SurveyedDate>
<DrawingNo>2017-MZ-001-PLN-020</DrawingNo>
<SheetNo>1 / 50</SheetNo>
<DrawingDate>1-7/03/2016</DrawingDate>
<Revision></Revision>
<UseLocaleLanguage>False</UseLocaleLanguage>
<MapTexts>
<MapText Label=”Client” Locale=”เจ้าของโครงการ”/>
<MapText Label=”Contractor” Locale=”ผู้รับจ้าง”/>
<MapText Label=”Drawn” Locale=”เขียน”/>
<MapText Label=”Design” Locale=”ออกแบบ”/>
<MapText Label=”Surveyor” Locale=”ผู้สำรวจ”/>
<MapText Label=”Surveyed Date” Locale=”วันที่สำรวจ”/>
<MapText Label=”Checked” Locale=”ตรวจสอบ”/>
<MapText Label=”Approved” Locale=”อนุมัติ”/>
<MapText Label=”Client Approved” Locale=”ผู้คุมงานอนุมัติ”/>
<MapText Label=”Drawing No.” Locale=”แบบเลขที่”/>
<MapText Label=”Plotted Date” Locale=”แบบวันที่”/>
<MapText Label=”Project” Locale=”โครงการ”/>
<MapText Label=”Drawing Title” Locale=”แผนที่แสดง”/>
<MapText Label=”Sheet No.” Locale=”แบบเลขที่”/>
<MapText Label=”Scale” Locale=”มาตราส่วน”/>
<MapText Label=”Vertical” Locale=”ทางดิ่ง”/>
<MapText Label=”Horizontal” Locale=”ทางราบ”/>
<MapText Label=”Vertical Scale” Locale=”มาตราส่วนทางดิ่ง”/>
<MapText Label=”Horizontal Scale” Locale=”มาตราส่วนทางราบ”/>
<MapText Label=”Legend” Locale=”สัญลักษณ์”/>
<MapText Label=”Note” Locale=”หมายเหตุ”/>
<MapText Label=”Geodetic Information” Locale=”ข้อมูลระบบพิกัด”/>
<MapText Label=”No.” Locale=”ครั้งที่”/>
<MapText Label=”Amendments” Locale=”ความเห็น”/>
<MapText Label=”By” Locale=”โดย”/>
<MapText Label=”Date” Locale=”วันที่”/>
<MapText Label=”Revision” Locale=”ครั้งที่แก้ไข”/>
</MapTexts>
</ProjectInfo>
<SectionOptions>
<VerticalScale>200.0</VerticalScale>
<HorizontalScale>2000.0</HorizontalScale>
<HozGridSpace>10.0</HozGridSpace>
<VertGridSpace>1.0</VertGridSpace>
<GridLineType>0</GridLineType>
<CalcIntersection>True</CalcIntersection>
<CalcArea>True</CalcArea>
<TrimTypical>False</TrimTypical>
<NumDecimalElev>3</NumDecimalElev>
<NumDecimalDist>3</NumDecimalDist>
<UseIntervalText>True</UseIntervalText>
<IntervalDist>20.0</IntervalDist>
<PrefixText></PrefixText>
<PostfixText>CD</PostfixText>
<UsePostPrefix>0</UsePostPrefix>
<UseOffsetElevFormat>2</UseOffsetElevFormat>
<CalcPlotAreaCut>True</CalcPlotAreaCut>
<CalcPlotAreaFill>True</CalcPlotAreaFill>
<NumVertCLLeft>70</NumVertCLLeft>
<NumVertCLRight>65</NumVertCLRight>
<NumHozTopBottom>21</NumHozTopBottom>
<LeftSideText>LT.</LeftSideText>
<RightSideText>RT.</RightSideText>
<StationText>Km.</StationText>
<SelectedTBlock>2</SelectedTBlock>
<NumSectionRows>4</NumSectionRows>
<NumSectionColumns>1</NumSectionColumns>
<SurveyType>0</SurveyType>
<PlotTBlock>True</PlotTBlock>
<SwapLeftAndRight>False</SwapLeftAndRight>
</SectionOptions>
<PageSetup>
<Size>A1</Size>
<Width>817</Width>
<Height>570</Height>
</PageSetup>
<Sections>
<NumSections>4</NumSections>
<Section Name=”-0+078″>
<XPositionOnPaper>0.0</XPositionOnPaper>
<YPositionOnPaper>0.0</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>94</NumPoints>
<Points>
<Point Elevation=”5.696″ Offset=”-600.0″/>
<Point Elevation=”5.693″ Offset=”-596.305″/>
<Point Elevation=”5.337″ Offset=”-587.506″/>
<Point Elevation=”2.271″ Offset=”-526.489″/>
<Point Elevation=”0.579″ Offset=”-507.151″/>
<Point Elevation=”-1.19″ Offset=”-490.954″/>
<Point Elevation=”-2.314″ Offset=”-471.14″/>
<Point Elevation=”-2.766″ Offset=”-462.616″/>
<Point Elevation=”-2.875″ Offset=”-457.815″/>
<Point Elevation=”-3.6″ Offset=”-434.419″/>
<Point Elevation=”-4.104″ Offset=”-414.736″/>
<Point Elevation=”-4.128″ Offset=”-406.659″/>
<Point Elevation=”-4.356″ Offset=”-380.964″/>
<Point Elevation=”-4.382″ Offset=”-376.491″/>
<Point Elevation=”-4.406″ Offset=”-375.215″/>
<Point Elevation=”-4.989″ Offset=”-349.559″/>
<Point Elevation=”-5.161″ Offset=”-338.86″/>
<Point Elevation=”-5.347″ Offset=”-322.669″/>
<Point Elevation=”-5.812″ Offset=”-302.883″/>
<Point Elevation=”-5.942″ Offset=”-297.584″/>
<Point Elevation=”-6.148″ Offset=”-283.939″/>
<Point Elevation=”-6.342″ Offset=”-272.05″/>
<Point Elevation=”-6.387″ Offset=”-267.862″/>
<Point Elevation=”-6.733″ Offset=”-246.07″/>
<Point Elevation=”-7.111″ Offset=”-231.836″/>
<Point Elevation=”-7.372″ Offset=”-217.745″/>
<Point Elevation=”-7.824″ Offset=”-194.494″/>
<Point Elevation=”-7.882″ Offset=”-191.371″/>
<Point Elevation=”-7.834″ Offset=”-183.933″/>
<Point Elevation=”-7.668″ Offset=”-165.389″/>
<Point Elevation=”-8.54″ Offset=”-139.979″/>
<Point Elevation=”-8.98″ Offset=”-123.408″/>
<Point Elevation=”-9.255″ Offset=”-115.989″/>
<Point Elevation=”-9.487″ Offset=”-93.719″/>
<Point Elevation=”-9.566″ Offset=”-88.009″/>
<Point Elevation=”-9.732″ Offset=”-85.635″/>
<Point Elevation=”-11.591″ Offset=”-57.803″/>
<Point Elevation=”-11.779″ Offset=”-43.322″/>
<Point Elevation=”-11.967″ Offset=”-27.782″/>
<Point Elevation=”-11.928″ Offset=”-5.492″/>
<Point Elevation=”-11.967″ Offset=”-1.247″/>
<Point Elevation=”-12.16″ Offset=”11.274″/>
<Point Elevation=”-12.318″ Offset=”20.29″/>
<Point Elevation=”-12.301″ Offset=”22.958″/>
<Point Elevation=”-12.249″ Offset=”39.553″/>
<Point Elevation=”-12.303″ Offset=”48.517″/>
<Point Elevation=”-12.25″ Offset=”59.398″/>
<Point Elevation=”-12.081″ Offset=”76.972″/>
<Point Elevation=”-12.057″ Offset=”79.758″/>
<Point Elevation=”-12.02″ Offset=”83.034″/>
<Point Elevation=”-11.703″ Offset=”98.323″/>
<Point Elevation=”-11.569″ Offset=”104.196″/>
<Point Elevation=”-11.562″ Offset=”105.697″/>
<Point Elevation=”-11.365″ Offset=”116.141″/>
<Point Elevation=”-11.25″ Offset=”117.347″/>
<Point Elevation=”-10.732″ Offset=”135.54″/>
<Point Elevation=”-10.625″ Offset=”146.227″/>
<Point Elevation=”-10.077″ Offset=”158.793″/>
<Point Elevation=”-8.681″ Offset=”177.76″/>
<Point Elevation=”-8.592″ Offset=”181.044″/>
<Point Elevation=”-8.401″ Offset=”194.035″/>
<Point Elevation=”-8.271″ Offset=”203.535″/>
<Point Elevation=”-7.923″ Offset=”213.249″/>
<Point Elevation=”-7.728″ Offset=”217.373″/>
<Point Elevation=”-7.095″ Offset=”235.24″/>
<Point Elevation=”-6.982″ Offset=”241.693″/>
<Point Elevation=”-6.682″ Offset=”247.206″/>
<Point Elevation=”-6.075″ Offset=”256.627″/>
<Point Elevation=”-5.455″ Offset=”278.481″/>
<Point Elevation=”-5.337″ Offset=”282.26″/>
<Point Elevation=”-5.042″ Offset=”294.845″/>
<Point Elevation=”-5.019″ Offset=”295.988″/>
<Point Elevation=”-4.347″ Offset=”321.161″/>
<Point Elevation=”-4.189″ Offset=”329.073″/>
<Point Elevation=”-3.808″ Offset=”348.654″/>
<Point Elevation=”-3.664″ Offset=”355.277″/>
<Point Elevation=”-3.317″ Offset=”372.887″/>
<Point Elevation=”-3.154″ Offset=”382.261″/>
<Point Elevation=”-2.776″ Offset=”410.279″/>
<Point Elevation=”-2.765″ Offset=”410.995″/>
<Point Elevation=”-2.704″ Offset=”413.887″/>
<Point Elevation=”-2.197″ Offset=”439.138″/>
<Point Elevation=”-1.967″ Offset=”448.435″/>
<Point Elevation=”-1.716″ Offset=”464.705″/>
<Point Elevation=”-1.481″ Offset=”485.302″/>
<Point Elevation=”-1.403″ Offset=”489.714″/>
<Point Elevation=”-1.251″ Offset=”498.925″/>
<Point Elevation=”-0.928″ Offset=”517.874″/>
<Point Elevation=”-0.799″ Offset=”529.258″/>
<Point Elevation=”-0.604″ Offset=”548.73″/>
<Point Elevation=”-1.169″ Offset=”576.146″/>
<Point Elevation=”-1.18″ Offset=”579.205″/>
<Point Elevation=”-1.305″ Offset=”585.252″/>
<Point Elevation=”-1.468″ Offset=”600.0″/>
</Points>
</Section>
<Section Name=”0+172″>
<XPositionOnPaper>0.0</XPositionOnPaper>
<YPositionOnPaper>0.0</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>104</NumPoints>
<Points>
<Point Elevation=”4.21″ Offset=”-600.0″/>
<Point Elevation=”3.865″ Offset=”-590.197″/>
<Point Elevation=”3.192″ Offset=”-572.372″/>
<Point Elevation=”1.492″ Offset=”-534.461″/>
<Point Elevation=”-1.278″ Offset=”-510.761″/>
<Point Elevation=”-1.773″ Offset=”-506.708″/>
<Point Elevation=”-2.262″ Offset=”-496.208″/>
<Point Elevation=”-3.027″ Offset=”-480.587″/>
<Point Elevation=”-3.26″ Offset=”-473.089″/>
<Point Elevation=”-4.034″ Offset=”-454.61″/>
<Point Elevation=”-4.281″ Offset=”-430.868″/>
<Point Elevation=”-4.355″ Offset=”-425.789″/>
<Point Elevation=”-4.35″ Offset=”-412.009″/>
<Point Elevation=”-4.367″ Offset=”-398.744″/>
<Point Elevation=”-4.437″ Offset=”-394.505″/>
<Point Elevation=”-4.911″ Offset=”-371.013″/>
<Point Elevation=”-5.369″ Offset=”-344.896″/>
<Point Elevation=”-5.909″ Offset=”-321.896″/>
<Point Elevation=”-5.972″ Offset=”-318.479″/>
<Point Elevation=”-6.203″ Offset=”-307.947″/>
<Point Elevation=”-6.559″ Offset=”-290.298″/>
<Point Elevation=”-6.742″ Offset=”-282.919″/>
<Point Elevation=”-7.278″ Offset=”-261.654″/>
<Point Elevation=”-7.19″ Offset=”-241.456″/>
<Point Elevation=”-7.206″ Offset=”-234.351″/>
<Point Elevation=”-7.744″ Offset=”-212.894″/>
<Point Elevation=”-7.972″ Offset=”-205.898″/>
<Point Elevation=”-8.011″ Offset=”-204.056″/>
<Point Elevation=”-8.937″ Offset=”-181.613″/>
<Point Elevation=”-9.613″ Offset=”-169.45″/>
<Point Elevation=”-10.568″ Offset=”-155.252″/>
<Point Elevation=”-11.306″ Offset=”-139.728″/>
<Point Elevation=”-11.435″ Offset=”-136.455″/>
<Point Elevation=”-11.67″ Offset=”-122.543″/>
<Point Elevation=”-11.836″ Offset=”-115.516″/>
<Point Elevation=”-11.845″ Offset=”-113.826″/>
<Point Elevation=”-12.223″ Offset=”-102.969″/>
<Point Elevation=”-12.219″ Offset=”-100.525″/>
<Point Elevation=”-12.572″ Offset=”-83.796″/>
<Point Elevation=”-12.666″ Offset=”-73.185″/>
<Point Elevation=”-12.605″ Offset=”-61.703″/>
<Point Elevation=”-12.379″ Offset=”-44.437″/>
<Point Elevation=”-12.45″ Offset=”-41.849″/>
<Point Elevation=”-12.273″ Offset=”-20.661″/>
<Point Elevation=”-12.271″ Offset=”-15.995″/>
<Point Elevation=”-12.279″ Offset=”-14.536″/>
<Point Elevation=”-12.193″ Offset=”7.86″/>
<Point Elevation=”-12.049″ Offset=”12.127″/>
<Point Elevation=”-11.97″ Offset=”26.792″/>
<Point Elevation=”-11.482″ Offset=”39.803″/>
<Point Elevation=”-11.428″ Offset=”47.755″/>
<Point Elevation=”-11.244″ Offset=”53.548″/>
<Point Elevation=”-10.805″ Offset=”70.223″/>
<Point Elevation=”-10.718″ Offset=”74.57″/>
<Point Elevation=”-10.702″ Offset=”75.913″/>
<Point Elevation=”-10.087″ Offset=”96.66″/>
<Point Elevation=”-9.799″ Offset=”103.819″/>
<Point Elevation=”-9.63″ Offset=”112.183″/>
<Point Elevation=”-9.234″ Offset=”126.254″/>
<Point Elevation=”-8.999″ Offset=”137.384″/>
<Point Elevation=”-8.476″ Offset=”159.55″/>
<Point Elevation=”-8.445″ Offset=”160.778″/>
<Point Elevation=”-8.271″ Offset=”164.407″/>
<Point Elevation=”-7.259″ Offset=”184.045″/>
<Point Elevation=”-7.127″ Offset=”189.256″/>
<Point Elevation=”-6.92″ Offset=”198.448″/>
<Point Elevation=”-6.48″ Offset=”207.958″/>
<Point Elevation=”-6.04″ Offset=”221.897″/>
<Point Elevation=”-5.474″ Offset=”242.469″/>
<Point Elevation=”-5.364″ Offset=”247.769″/>
<Point Elevation=”-5.253″ Offset=”251.096″/>
<Point Elevation=”-4.998″ Offset=”265.24″/>
<Point Elevation=”-4.818″ Offset=”275.102″/>
<Point Elevation=”-4.715″ Offset=”277.979″/>
<Point Elevation=”-4.027″ Offset=”298.699″/>
<Point Elevation=”-3.738″ Offset=”309.727″/>
<Point Elevation=”-3.575″ Offset=”322.408″/>
<Point Elevation=”-3.105″ Offset=”340.851″/>
<Point Elevation=”-2.937″ Offset=”346.115″/>
<Point Elevation=”-2.855″ Offset=”359.315″/>
<Point Elevation=”-2.833″ Offset=”366.127″/>
<Point Elevation=”-2.816″ Offset=”367.498″/>
<Point Elevation=”-2.675″ Offset=”385.31″/>
<Point Elevation=”-2.596″ Offset=”391.895″/>
<Point Elevation=”-2.673″ Offset=”406.1″/>
<Point Elevation=”-2.465″ Offset=”418.482″/>
<Point Elevation=”-2.516″ Offset=”427.315″/>
<Point Elevation=”-2.328″ Offset=”445.356″/>
<Point Elevation=”-2.337″ Offset=”446.342″/>
<Point Elevation=”-2.626″ Offset=”452.604″/>
<Point Elevation=”-3.486″ Offset=”470.977″/>
<Point Elevation=”-3.693″ Offset=”476.912″/>
<Point Elevation=”-3.777″ Offset=”496.074″/>
<Point Elevation=”-3.932″ Offset=”501.72″/>
<Point Elevation=”-4.018″ Offset=”513.362″/>
<Point Elevation=”-3.474″ Offset=”529.259″/>
<Point Elevation=”-3.265″ Offset=”532.878″/>
<Point Elevation=”-2.893″ Offset=”543.264″/>
<Point Elevation=”-2.834″ Offset=”544.691″/>
<Point Elevation=”-2.372″ Offset=”551.966″/>
<Point Elevation=”-1.374″ Offset=”568.651″/>
<Point Elevation=”-1.184″ Offset=”572.608″/>
<Point Elevation=”-0.9″ Offset=”589.873″/>
<Point Elevation=”-0.265″ Offset=”600.0″/>
</Points>
</Section>
<Section Name=”0+422″>
<XPositionOnPaper>0.0</XPositionOnPaper>
<YPositionOnPaper>0.0</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>98</NumPoints>
<Points>
<Point Elevation=”2.527″ Offset=”-600.0″/>
<Point Elevation=”1.923″ Offset=”-583.886″/>
<Point Elevation=”1.147″ Offset=”-573.7″/>
<Point Elevation=”0.148″ Offset=”-560.844″/>
<Point Elevation=”-1.561″ Offset=”-534.717″/>
<Point Elevation=”-1.604″ Offset=”-534.134″/>
<Point Elevation=”-2.764″ Offset=”-502.908″/>
<Point Elevation=”-3.147″ Offset=”-490.643″/>
<Point Elevation=”-3.634″ Offset=”-472.527″/>
<Point Elevation=”-4.199″ Offset=”-442.515″/>
<Point Elevation=”-4.26″ Offset=”-440.183″/>
<Point Elevation=”-4.353″ Offset=”-435.493″/>
<Point Elevation=”-4.626″ Offset=”-412.074″/>
<Point Elevation=”-4.836″ Offset=”-400.43″/>
<Point Elevation=”-4.963″ Offset=”-383.964″/>
<Point Elevation=”-5.335″ Offset=”-361.676″/>
<Point Elevation=”-5.378″ Offset=”-357.711″/>
<Point Elevation=”-5.461″ Offset=”-354.483″/>
<Point Elevation=”-5.587″ Offset=”-348.617″/>
<Point Elevation=”-6.599″ Offset=”-306.504″/>
<Point Elevation=”-6.923″ Offset=”-292.309″/>
<Point Elevation=”-7.646″ Offset=”-260.816″/>
<Point Elevation=”-7.665″ Offset=”-260.005″/>
<Point Elevation=”-8.267″ Offset=”-238.897″/>
<Point Elevation=”-9.466″ Offset=”-215.915″/>
<Point Elevation=”-9.939″ Offset=”-205.494″/>
<Point Elevation=”-10.636″ Offset=”-192.184″/>
<Point Elevation=”-11.499″ Offset=”-177.238″/>
<Point Elevation=”-11.845″ Offset=”-170.731″/>
<Point Elevation=”-12.281″ Offset=”-152.944″/>
<Point Elevation=”-12.343″ Offset=”-150.873″/>
<Point Elevation=”-12.345″ Offset=”-150.114″/>
<Point Elevation=”-12.485″ Offset=”-127.286″/>
<Point Elevation=”-12.474″ Offset=”-120.381″/>
<Point Elevation=”-12.427″ Offset=”-111.488″/>
<Point Elevation=”-12.447″ Offset=”-105.883″/>
<Point Elevation=”-12.651″ Offset=”-87.792″/>
<Point Elevation=”-12.622″ Offset=”-78.078″/>
<Point Elevation=”-12.53″ Offset=”-55.591″/>
<Point Elevation=”-12.495″ Offset=”-52.513″/>
<Point Elevation=”-12.115″ Offset=”-31.23″/>
<Point Elevation=”-12.009″ Offset=”-23.783″/>
<Point Elevation=”-11.488″ Offset=”-9.776″/>
<Point Elevation=”-11.236″ Offset=”4.184″/>
<Point Elevation=”-10.416″ Offset=”13.232″/>
<Point Elevation=”-10.641″ Offset=”20.134″/>
<Point Elevation=”-10.28″ Offset=”44.002″/>
<Point Elevation=”-10.292″ Offset=”44.652″/>
<Point Elevation=”-10.289″ Offset=”44.792″/>
<Point Elevation=”-10.828″ Offset=”56.567″/>
<Point Elevation=”-10.797″ Offset=”59.917″/>
<Point Elevation=”-10.401″ Offset=”76.981″/>
<Point Elevation=”-10.186″ Offset=”85.234″/>
<Point Elevation=”-10.076″ Offset=”91.139″/>
<Point Elevation=”-9.671″ Offset=”106.374″/>
<Point Elevation=”-9.556″ Offset=”114.729″/>
<Point Elevation=”-9.018″ Offset=”131.881″/>
<Point Elevation=”-8.96″ Offset=”133.976″/>
<Point Elevation=”-8.938″ Offset=”135.194″/>
<Point Elevation=”-8.59″ Offset=”157.634″/>
<Point Elevation=”-8.516″ Offset=”163.615″/>
<Point Elevation=”-8.379″ Offset=”182.167″/>
<Point Elevation=”-8.093″ Offset=”194.118″/>
<Point Elevation=”-7.878″ Offset=”204.774″/>
<Point Elevation=”-7.548″ Offset=”221.957″/>
<Point Elevation=”-7.482″ Offset=”225.607″/>
<Point Elevation=”-7.519″ Offset=”237.596″/>
<Point Elevation=”-7.537″ Offset=”244.959″/>
<Point Elevation=”-7.524″ Offset=”247.228″/>
<Point Elevation=”-7.202″ Offset=”265.159″/>
<Point Elevation=”-6.937″ Offset=”273.625″/>
<Point Elevation=”-6.791″ Offset=”280.238″/>
<Point Elevation=”-6.674″ Offset=”286.833″/>
<Point Elevation=”-6.486″ Offset=”295.474″/>
<Point Elevation=”-6.408″ Offset=”313.252″/>
<Point Elevation=”-6.365″ Offset=”316.221″/>
<Point Elevation=”-6.294″ Offset=”327.463″/>
<Point Elevation=”-6.2″ Offset=”337.22″/>
<Point Elevation=”-6.207″ Offset=”338.339″/>
<Point Elevation=”-5.867″ Offset=”355.513″/>
<Point Elevation=”-5.65″ Offset=”362.307″/>
<Point Elevation=”-5.567″ Offset=”375.271″/>
<Point Elevation=”-5.433″ Offset=”387.259″/>
<Point Elevation=”-5.152″ Offset=”396.436″/>
<Point Elevation=”-4.378″ Offset=”413.203″/>
<Point Elevation=”-4.146″ Offset=”425.427″/>
<Point Elevation=”-4.099″ Offset=”435.841″/>
<Point Elevation=”-3.993″ Offset=”439.726″/>
<Point Elevation=”-3.418″ Offset=”457.353″/>
<Point Elevation=”-2.943″ Offset=”467.452″/>
<Point Elevation=”-2.548″ Offset=”480.037″/>
<Point Elevation=”-1.52″ Offset=”499.033″/>
<Point Elevation=”-1.085″ Offset=”505.573″/>
<Point Elevation=”-0.707″ Offset=”509.068″/>
<Point Elevation=”-0.564″ Offset=”514.17″/>
<Point Elevation=”1.044″ Offset=”545.131″/>
<Point Elevation=”3.671″ Offset=”586.351″/>
<Point Elevation=”4.257″ Offset=”600.0″/>
</Points>
</Section>
<Section Name=”0+672″>
<XPositionOnPaper>0.0</XPositionOnPaper>
<YPositionOnPaper>0.0</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>101</NumPoints>
<Points>
<Point Elevation=”0.751″ Offset=”-600.0″/>
<Point Elevation=”0.17″ Offset=”-590.282″/>
<Point Elevation=”-0.185″ Offset=”-585.509″/>
<Point Elevation=”-1.698″ Offset=”-564.455″/>
<Point Elevation=”-2.012″ Offset=”-556.049″/>
<Point Elevation=”-2.541″ Offset=”-536.428″/>
<Point Elevation=”-2.738″ Offset=”-526.646″/>
<Point Elevation=”-3.164″ Offset=”-510.314″/>
<Point Elevation=”-3.581″ Offset=”-498.217″/>
<Point Elevation=”-3.967″ Offset=”-482.548″/>
<Point Elevation=”-4.17″ Offset=”-466.638″/>
<Point Elevation=”-4.63″ Offset=”-454.111″/>
<Point Elevation=”-4.885″ Offset=”-437.444″/>
<Point Elevation=”-4.739″ Offset=”-426.605″/>
<Point Elevation=”-4.755″ Offset=”-407.163″/>
<Point Elevation=”-4.887″ Offset=”-399.338″/>
<Point Elevation=”-5.348″ Offset=”-377.159″/>
<Point Elevation=”-5.455″ Offset=”-371.865″/>
<Point Elevation=”-6.085″ Offset=”-345.987″/>
<Point Elevation=”-6.145″ Offset=”-343.34″/>
<Point Elevation=”-6.428″ Offset=”-331.877″/>
<Point Elevation=”-6.448″ Offset=”-330.864″/>
<Point Elevation=”-7.104″ Offset=”-309.854″/>
<Point Elevation=”-7.356″ Offset=”-296.783″/>
<Point Elevation=”-7.378″ Offset=”-295.786″/>
<Point Elevation=”-8.31″ Offset=”-272.558″/>
<Point Elevation=”-9.344″ Offset=”-249.668″/>
<Point Elevation=”-9.524″ Offset=”-246.49″/>
<Point Elevation=”-10.115″ Offset=”-236.3″/>
<Point Elevation=”-10.351″ Offset=”-218.394″/>
<Point Elevation=”-10.714″ Offset=”-213.348″/>
<Point Elevation=”-11.674″ Offset=”-204.751″/>
<Point Elevation=”-11.699″ Offset=”-201.381″/>
<Point Elevation=”-11.744″ Offset=”-193.463″/>
<Point Elevation=”-12.329″ Offset=”-178.033″/>
<Point Elevation=”-12.592″ Offset=”-165.284″/>
<Point Elevation=”-12.777″ Offset=”-155.308″/>
<Point Elevation=”-12.43″ Offset=”-144.408″/>
<Point Elevation=”-12.528″ Offset=”-139.081″/>
<Point Elevation=”-12.669″ Offset=”-133.221″/>
<Point Elevation=”-12.262″ Offset=”-121.876″/>
<Point Elevation=”-12.13″ Offset=”-110.999″/>
<Point Elevation=”-12.286″ Offset=”-98.019″/>
<Point Elevation=”-12.367″ Offset=”-89.578″/>
<Point Elevation=”-11.654″ Offset=”-75.025″/>
<Point Elevation=”-11.598″ Offset=”-71.369″/>
<Point Elevation=”-12.418″ Offset=”-63.45″/>
<Point Elevation=”-12.586″ Offset=”-57.395″/>
<Point Elevation=”-12.029″ Offset=”-40.428″/>
<Point Elevation=”-11.941″ Offset=”-37.225″/>
<Point Elevation=”-12.05″ Offset=”-17.903″/>
<Point Elevation=”-11.919″ Offset=”-14.255″/>
<Point Elevation=”-10.878″ Offset=”5.213″/>
<Point Elevation=”-10.987″ Offset=”7.781″/>
<Point Elevation=”-12.241″ Offset=”17.876″/>
<Point Elevation=”-12.175″ Offset=”19.902″/>
<Point Elevation=”-12.679″ Offset=”37.309″/>
<Point Elevation=”-12.779″ Offset=”41.445″/>
<Point Elevation=”-12.756″ Offset=”41.967″/>
<Point Elevation=”-12.147″ Offset=”62.308″/>
<Point Elevation=”-12.189″ Offset=”63.587″/>
<Point Elevation=”-11.804″ Offset=”83.447″/>
<Point Elevation=”-11.748″ Offset=”85.224″/>
<Point Elevation=”-11.68″ Offset=”105.223″/>
<Point Elevation=”-11.648″ Offset=”107.415″/>
<Point Elevation=”-11.299″ Offset=”124.477″/>
<Point Elevation=”-11.183″ Offset=”129.8″/>
<Point Elevation=”-10.608″ Offset=”145.77″/>
<Point Elevation=”-10.491″ Offset=”152.394″/>
<Point Elevation=”-10.528″ Offset=”156.006″/>
<Point Elevation=”-10.486″ Offset=”170.955″/>
<Point Elevation=”-10.376″ Offset=”180.254″/>
<Point Elevation=”-10.183″ Offset=”192.924″/>
<Point Elevation=”-10.122″ Offset=”198.333″/>
<Point Elevation=”-9.995″ Offset=”209.165″/>
<Point Elevation=”-9.803″ Offset=”220.192″/>
<Point Elevation=”-9.727″ Offset=”230.156″/>
<Point Elevation=”-9.793″ Offset=”235.687″/>
<Point Elevation=”-9.444″ Offset=”249.066″/>
<Point Elevation=”-9.308″ Offset=”258.031″/>
<Point Elevation=”-9.253″ Offset=”261.971″/>
<Point Elevation=”-8.511″ Offset=”283.111″/>
<Point Elevation=”-8.163″ Offset=”289.464″/>
<Point Elevation=”-7.102″ Offset=”307.459″/>
<Point Elevation=”-6.902″ Offset=”312.383″/>
<Point Elevation=”-6.731″ Offset=”322.229″/>
<Point Elevation=”-6.609″ Offset=”326.578″/>
<Point Elevation=”-6.011″ Offset=”346.572″/>
<Point Elevation=”-5.822″ Offset=”350.33″/>
<Point Elevation=”-4.461″ Offset=”371.66″/>
<Point Elevation=”-4.318″ Offset=”373.491″/>
<Point Elevation=”-1.818″ Offset=”396.911″/>
<Point Elevation=”-1.583″ Offset=”398.654″/>
<Point Elevation=”1.249″ Offset=”416.959″/>
<Point Elevation=”1.897″ Offset=”437.491″/>
<Point Elevation=”3.568″ Offset=”467.863″/>
<Point Elevation=”6.567″ Offset=”551.744″/>
<Point Elevation=”6.606″ Offset=”552.895″/>
<Point Elevation=”6.604″ Offset=”553.18″/>
<Point Elevation=”6.591″ Offset=”553.868″/>
<Point Elevation=”5.965″ Offset=”600.0″/>
</Points>
</Section>
</Sections>
</root>

[/sourcecode]

จบตอนที่ 2

  • พบกันตอนหน้า ตอนที่ 3 ครับ

สิ้นสุดการรอคอย XSection Plot รุ่น 4.10 มาแล้ว ตอนที่ 1

ถึงจะล่าช้าแต่ก็มาตามสัญญา

  • โปรแกรมเขียนรูปตัดตามขวาง XSection Plot รุ่นนี้รุ่น 4.10 มาแทนรุ่นเดิม 3.25 ที่ปลดระวางด้วยเนื่องจากอายุมากร่วมสองทศวรรษ แต่ขอแจ้งไว้ก่อนรุ่นนี้ยังเป็นรุ่นทดลองใช้ (beta) นะครับ เพราะยังมีบั๊กอยู่บ้าง แต่โดยรวมใช้งานได้

xsectionplotv410-intro

ความเป็นมาของโปรแกรม

  • อ่านความเป็นมาของโปรแกรมได้อย่างละเอียดที่ => ก้าวต่อไปของโปรแกรมเขียนรูปตัดตามขวาง XSection Plot ในร่างใหม่พัฒนาด้วย Python + Qt
  • และที่ => ก้าวที่ใกล้กับโปรแกรมเขียนรูปตัดตามขวาง XSection Plot รุ่นใหม่
  • ก็ขอเท้าความเป็นมาโปรแกรมสักนิดเผื่อผู้อ่านไม่ได้ติดตามมาก่อน โปรแกรมเขียนรูปตัดตามขวาง XSection Plot รุ่นเดิมพัฒนาด้วย VB อายุอานามเกือบยี่สิบปีแล้วครับ ใช้ได้ดีตอนวินโดส์ XP แต่หลังมาวินโดส์ 7, 8 และวินโดส์ 10 ก็มีปัญหากับการติดตั้งเรื่อยมา เพราะโปรแกรมยังใช้ library ของวินโดส์รุ่นเก่า พอเจอวินโดส์ใหม่หาไลบรารีไม่เจอ ผมเขียนใหม่ด้วยภาษาไพธอน (Python) ใช้ระบบกราฟฟิคเฟรมเวิร์คของ Qt รวมๆเรียกว่า PyQt
  • ขณะที่พัฒนานี้ใช้ Python 3.5 ส่วน Qt ใช้ PyQt รุ่น 5.6 ส่วน Qt เป็นเฟรมเวิร์คที่ใหญ่โตมากๆ อ่านคู่มือไม่ไหว ก็จะเลือกอ่านแต่เท่าที่ใช้ แน่นอนสิ่งที่ทำให้ผมโปรแกรมได้สำเร็จยกเครดิตให้ stackoverflow.com ถ้าไม่มีเวปไซต์ถามและตอบปัญหาการเขียนโปรแกรมนี้ ยังคิดว่าจะเขียนโปรแกรมได้อย่างไร มีปัญหาอะไร เข้าไปค้นหาทีไร ก็เจอคำตอบทุกที จะมีคนที่มีปัญหาแบบเรามาถามก่อนเรา พร้อมมีคนมาตอบให้ ส่วนใหญ่ 99% คำตอบสามารถนำไปใช้งานต่อยอดได้

สาเหตุแห่งความล่าช้า

  • เรื่องความรู้ความเข้าใจภาษาไพธอน อาจจะไม่ใช่ปัญหานักเนื่องจากไพธ่อนเป็นภาษาที่เรียบง่ายแต่ทรงพลัง ปัญหาของผมลำดับแรกคือ Qt Framework ที่เกี่ยวกับกราฟฟิค ศึกษาได้ค่อนข้างช้า ก่อนหน้านี้ไปทุ่มให้กับ wxPython ที่เป็น GUI Framework ที่ใช้ wxWidget ทำให้เสียเวลาไปมาก  พูดอย่างนี้ไม่ใช่ wxPython ไม่ดีนะครับเพียงแต่ส่วนตัวแล้วไม่ชอบ มาเจอ Qt นี่ใช่เลย
  • ความล่าช้าลำดับที่สอง คือไลบรารีที่ผมเอามาเขียนไฟล์ Autocad DXF ใช้ ezdxf เป็นโครงการเปิดซอร์ส ตัวไลบรารีใช้งานง่าย เก่งมาก ผมลองสมมุติว่าถ้าให้ผมเขียนไลบรารีนี้เอง คงไม่มีปัญญาเป็นแน เพราะผู้พัฒนาเข้าใจถ่องแท้มาก ทั้งโครงสร้างของ  DXF ที่รุ่นหลังๆค่อนข้างซับซ้อนพอสมควร ปัญหาของผมอยู่ที่คอมไพล์โปรแกรมเพื่อสร้างไฟล์ execute ที่เป็นไบนารี .exe ตอนแรกใช้ py2exe กลับไม่สามารถรันได้ พอเปิดโปรแกรมใช้งานพอเรียกใช้ไลบรารีนี้จะ crash ทันที ตอนแรกหาสาเหตุไม่เจอ ตอนหลังมาทราบว่ามี่ไฟล์ที่ไลบรารี ezdxf ที่ต้องการใช้ แต่ py2exe ไม่ได้ใส่ไปให้ด้วย คือ py2exe จะสร้างไฟล์ไลบรารีที่โปรแกรมเรียกใช้ทั้งหมดแล้ว zip ไว้ แต่ขาดไฟล์บางไฟล์ของ ezdxf
  • ผมหันมาใช้ pyinstaller คอมไพล์และสร้างไฟล์  .exe สำเร็จเนื่องจาก pyinstaller ใช้วิธีการที่ง่ายกว่าแบบคิดไม่ถึงและก็ยืดหยุ่นมาก ตอนนี้เทใจให้กับ pyinstaller เมื่อได้ไฟล์ execute มาก็ไม่ยากแล้วทำไฟล์ติดตั้ง (setup) ด้วย Inno Setup เจ้าเดิมที่ผมใช้ประจำ เพราะฟรี ใช้งานง่าย

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

  • เนื่องจากจุดกำเนิดโปรแกรมนี้เริ่มต้นในยุค PC กับวินโดส์ที่กำลังรุ่งเรืองคือวินโดส์ 98 ที่โปรแกรมด้าน Civil/Survey ยังไม่เก่ง การเขียนรูปตัดจึงค่อนข้างจะใช้มือแบบแมนวลคือเน้นป้อนเข้าตาราง ต่อมาเริ่มมีโปรแกรมด้าน Civil/Survey เช่น Land desktop, Terramodel, โปรแกรมตระกูล Microstation เช่น PowerCivil, Inroads ประมาณนี้ หลายๆโปรแกรมสามารถ export รูปตัดเป็น text file ได้ ผมก็พัฒนาโปรแกรมให้สามารถอ่านเข้ามาโปรแกรม XSection Plot เพื่อจัดรูปแบบและสามารถนำไฟล์เข้าโปรแกรมจำพวก CAD ได้
  • ปัจจุบัน Terramodel, Land desktop หยุดพัฒนาไปหลายปี แต่ผู้พัฒนาหันมาพัฒนาตัวใหม่ที่เก่งกว่าเดิมเช่น Trimble Business Center (TBC) ของค่าย Trimble และค่าย Autodesk ก็มี Autodesk Civil3D เป็นโปรดัคเรือธง ผมลองใช้มาหมดแล้วครับ ยอมรับความสามารถเก่งกาจไปไกล แต่ราคาต่อไลเซนต์ก็สูงมากโข ถามกันจริงๆ ถ้าจะซื้อมาใช้แบบไลเซนต์ จะมีผู้ใช้ที่ไม่ใช่องค์กรสักกี่คนที่มีปัญญาซื้อมาใช้งานได้ นี่เป็นปัญหาอย่างหนึ่ง

ดาวน์โหลดและติดตั้ง

  • ดาวน์โหลดได้ที่ XSectionPlotV410b867Setup64.zip หรือดูด้านขวามือตรง ดาวน์โหลด (Download) มองหาลิ๊งค์สำหรับติดตั้งโปรแกรม  “XSection Plot V4.10” ในเบื้องต้นมีเฉพาะรุ่น 64 บิต (สำหรับรุ่น 32 บิต รออีกสักพักครับ) ดาวน์โหลดมาแล้ว ขนาดโปรแกรมไม่ใหญ่ประมาณ 16 MB จากนั้นก็ติดตั้งได้ง่ายๆ ติดตั้งแล้วจะเห็น icon ขึ้นมาบน Desktop ดังรูปด้านล่าง

xsectionplot_icon

พาทัวร์โปรแกรมทดลองใช้งาน

  • ต่อไปผมจะพาลองทดลองใช้โปรแกรมคร่าวๆ พาทัวร์การใช้งานรวดเดียวจบ ผมมีไฟล์ตัวอย่าง ตอนติดตั้งโปรแกรมจะไปอยู่ในโฟลเดอร์ของวินโดส์ C:\Users\ชื่อผู้ใช้\AppData\Roaming\XSection Plot\examples data โฟลเดอร์นี้จะถูกซ่อนอัตโนมัติ ผู้ใช้หน้าใหม่ๆอาจจะหาไม่เจอ กรณีหาไม่เจอผมเตรียมตัวช่วยไว้
  • เปิดโปรแกรมมาครั้งแรกจะเห็นว่าตารางโล่งไม่มีข้อมูล

xsectionplotv410-firstload

1.ที่จัดเก็บข้อมูลตัวอย่าง

  • เปิดเมนู Help เลือกคลิกที่ “Example data”

help_examples_data

  • โปรแกรมจะเปิด windows explorer ของวินโดส์มาให้ มองไปจะเห็นโฟลเดอร์ “examples data”

examples_data_folder

  • จากนั้น double click เข้าไปที่โฟลเดอร์ “example data\mz\import files” จะเห็นไฟล์อยู่สองไฟล์ มาถึงตรงนี้ขอให้คลิกแถบ path ดังรูปแล้วก็ใช้เมาส์คลิกขวาเลือก copy ตอนนี้ path อยู่ในคลิปบอร์ดแล้ว ต่อไปจะใช้โปรแกรม XSection Plot ทำการ import ข้อมูลที่อยู่ในพาทนี้เข้าโปรแกรม

mz_import_files_folder

2.นำข้อมูลดินเดิมเข้าโปรแกรม (Import file)

  • คลิก Import text file ที่ไอคอนตามรูปเพื่อจะนำเข้าไฟล์รูปตัด จะเห็นไดอะล็อกของ Import XSection text file ขึ้นมา คลิกที่ไอคอน Open เพื่อเปิดไฟล์

xtoolbar_import

import_dialog

  • จะเห็นไดอะล็อก “Open X-Section text file” เอาเมาส์ไปคลิกที่แถบพาทดังรูปแล้ว paste กด enter สักครั้งหนึ่งจะเห็นไฟล์ app_ch155m.xsc ที่เก็บไฟล์รูปตัดไว้ จากนั้นคลิกเลือกไฟล์นี้

xfolder_import

  • โปรแกรมจะเปิดไฟล์แล้วอ่านรูปตัดแสดง preview ไฟล์รูปตัด สังเกตว่ารูปแบบค่าปริยายจะเลือก Terramodel Road (4 decimal) คลิก “Next >” เพื่อไปขั้นตอนต่อไป

import_view

3.เลือกรูปตัดให้พอดีกับหน้ากระดาษแบบ

  • เมื่อโปรแกรมอ่านพบรูปตัดแล้วจะนำรูปตัดมาแสดงเป็นลิสต์เพื่อจะให้ผู้ใช้เลือกรูปตัดไปใส่กระดาษแบบตามที่ต้องการ ดูรูปข้างล่าง ข้อสังเกตจะเห็นขั้นตอนที่  3.เลือกปลายทาง ไฟล์นี้เป็นค่าระดับดินเดิมจึงเลือก Copy to Existing Ground section ขั้นตอนที่ 4.copy ไปตารางข้อมูล เป็นอันเรียบร้อย

import_selected

  • ข้อมูลรูปตัดจะถูก  copy ตารางป้อนข้อมูลดินเดิม (Existing Ground Section)

xsectionplot_import_ground

4.นำเข้าข้อมูลไฟล์ Typical Section

  • ต่อไปจะนำข้อมูลดินเดิม Typical section  จาก toolbar เลือกไอคอน “Import text file” เปิดไฟล์ที่โฟลเดอร์เดิม ที่นี้เลือกไฟล์ “design_ch.xsc” คลิก Open โปรแกรมจะเปิดไฟล์มาให้ดูก่อนจากนั้นคลิก “Next >” เพื่อมาเลือกรูปตัด เลือกตามรูปโดยเฉพาะขั้นตอนที่ 3.เลือกปลายทาง เลือก “Copy to Typical section” สุดท้ายคลิกที่ “Copy to >>

import_selected_ts

  • โปรแกรมจะนำข้อมูล Typical มาลงที่ตารางข้อมูล Typical section

typical_after_imported

5.บันทึกไฟล์ข้อมูล (Save file)

  • เมื่อนำข้อมูลเข้ามาแล้ว สิ่งแรกควรจะบันทึกไฟล์ข้อมูลลงดิสค์ คลิกที่ไอคอน “Save” เนื่องจาก Tab มีสองหน้าคือ Typical Section และ Existing Ground Section เวลาจะบันทึกไฟล์ข้อมูลแบบไหนก็ให้คลิกที่แท็บให้ตรงกัน เช่นถ้าต้องการบันทึกไฟล์ข้อมูลของดินเดิมก็ให้คลิกที่แท็บ “Existing Ground Section”

toolbar_save

  • ตอนนี้อยู่แท็บ “Typical Section” เราจะบันทึกไฟล์ข้อมูลนี้ก่อน ผมจะเก็บใน Documents ซึ่ง extension ของไฟล์จะเป็น .txml โดยอัตโนมัต ไฟล์ข้อมูลนี้เป็น xml เปิดดูได้ด้วย Notepad ของวินโดส์ ส่วนรูปแบบฟอร์แมตของไฟล์รุ่นเก่าเป็นไบนารีมีนามสกุล .XST

save_typical_section_file

  • บันทึกไฟล์ดินเดิม (Existing Ground Section) คลิกที่แท็บ “Existing Ground Section” เพื่อสวิตช์มาที่ตารางข้อมูลดินเดิม คลิก “Save” ที่ toolbar ด้านบน เลือกโฟลเดอร์ Documents ป้อนชื่อไฟล์ดังรูปด้านล่าง ไฟล์จะมี extension เป็น .gxml อัตโนมัติsave_existing_ground_section

จบตอนที่ 1 ติดตามต่อตอนที่ 2

  • เมื่อบันทึกข้อมูลรูปตัดลงไฟล์แล้วเราสามารถปิดโปรแกรมได้ โดยที่ไม่ต้องเริ่มต้นใหม่เมื่อเปิดโปรแกรมอีกครั้ง ก็ขอจบตอนที่ 1 ก่อน เพราะจะยาวมากเกินไปติดตามตอนที่ 2 ต่อได้ครับ

 

ก้าวที่ใกล้กับโปรแกรมเขียนรูปตัดตามขวาง XSection Plot รุ่นใหม่

ก้าวที่ผ่านมา

  • ผ่านมาครึ่งปีกว่าแล้วก็ยังไม่เสร็จตามสัญญา แต่โปรแกรมก็คืบหน้าไปพอสมควรครับ ขอเท้าถึงความหลังหน่อยว่าโปรแกรมรุ่นเดิมเขียนด้วย VB ที่นับอายุอานามแล้วจะใกล้ๆ 20 ปีเข้าไปแล้ว ผมนำโปรแกรมมาพัฒนาใหม่ด้วยของฟรีดีมีคุณภาพ Python3 + Qt5 แรกก็ตะกุกตะกักพอสมควรเนื่องจากยังใหม่กับ Python และเป็นครั้งแรกที่ใช้  Qt สำหรับ Python ที่ว่ากันว่าเรียนง่ายเขียนง่าย แต่สำหรับผมที่เขียนด้วยภาษาอื่นมานมนานก็ต้องบอกว่า Python มีสไตล์ของตัวเอง ที่สั้น กระชับ ฉับไวที่คาดไม่ถึงเลยก็หลายๆอย่าง แต่ด้วยความที่สมองคิดแบบภาษาอื่นมานาน พอมาลอง Python ก็ยอมรับว่าตอนใหม่ๆไปอย่างฝืด Learning Curve  ชันไปนิดสำหรับผม แต่ผ่านไปปีกว่าๆ สไตล์ของ Python เริ่มเข้าหัว ทุกสิ่งทุกอย่างจะลื่นไหลออกมาเอง ลองมาดูว่าโปรแกรมสามารถทำอะไรได้บ้างเมื่อเทียบกับรุ่นเก่าที่พัฒนาด้วย VB 6

XSection_41_showcase

1) ป้อนและแก้ไขข้อมูลโครงการ (Project Information)

  • ก็ยังเหมือนเดิมครับ ที่ข้อมูลของรูปตัดจะต้องมีข้อมูลโครงการเป็นตัวกำกับว่า งานอะไร ใครเป็นเจ้าของงาน ผู้รับจ้างเป็นใคร ช่วงวันและเวลา ข้อมูลนี้จะถูกเขียนเข้าไปในไฟล์ข้อมูลตามปกติ ตอนนี้ในส่วนติดต่อผู้ใช้ ผมออกแบบเป็นภาษาอังกฤษทั้งหมด ภาษาไทยจะมีรุ่นจริงที่ออกมาภายหลัง

XSectionPlot_41_Projinfo

  • บางครั้งผู้ใชอาจต้อง Titleblock เป็นภาษาไทย ในส่วนรูปแบบ Titleblock ที่ออกแบบไว้ในโปรแกรมเป็นภาษาอังกฤษ ถ้าจะใช้ภาษาไทยต้องมาป้อนคำเทียบกับภาษาอังกฤษเอง หรือจะใช้ที่ผมใส่ไว้เป็น default ก็ได้

XSectionPlot_41_Maptb

2) ตั้งขนาดกระดาษ (Page Setup)

  • การเขียนรูปตัดตามขวางเป็นงานจัดทำแผนที่ อันดับแรกต้องกำหนดขนาดกระดาษก่อน บางครั้งขนาดกระดาษขึ้นอยู่กับข้อกำหนดของเจ้าของงาน ในโปรแกรมนี้สามารถตั้งขนาดกระดาษได้ แต่จำกัดเฉพาะขนาดกระดาษที่เป็น ISO และก็เสริมขนาดกระดาษที่กำหนดโดยผู้ใช้

XSectionPlot_PageSetup

3) ตั้งค่ารูปตัดตามขวาง (Section Settings)

  • การตั้งค่ารูปตัดตามขวางเช่นการตั้งมาตราส่วนทางราบ (Horizontal scale) และมาตราส่วนทางดิ่ง (Vertical scale) ให้เหมาะสมหรือตามแต่เจ้าของงานจะกำหนด อย่างอื่นได้แก่ ตั้งจำนวนทศนิยมตัวเลข รูปแบบของเส้นกริด

XSection_41_gensettings

  • รูปแบบตัวเลขค่าระดับ และระยะทาง offset เลือกตั้งได้ 3 รูปแบบ

XSectionPlot_41_elevdisttxt

  • รูปแบบค่าระดับข้างตารางกริด สามารถตั้งตัวอักษรเป็นตัวย่อนำหน้าเพื่อระบุว่าเป็นค่าระดับที่ใช้อ้างอิงกับหลักฐานทางดิ่ง (Datum) อะไรเช่น รทก (MSL) หรือไม่ก็ LAT (Low Astronomical Tide) หรือบางครั้ง CD (Chart Datum)

XSectionPlot_41_gridelevtxt

  • ตั้งค่าระดับของกริดเส้นบนสุด บางครั้งรูปตัดของงานอาจจะสูงๆต่ำๆ ไม่เท่ากัน ตัวเลขนี้สามารถตั้งค่าเหมาะสมให้ได้ เพื่อให้สามารถแสดงเส้น Existing ground และ Typical section ได้พอดีไม่หลุดด้านบนหรือด้านล่าง เพื่อไม่ให้ผู้ใช้ต้องลองผิดลองถูก สามารถให้โปรแกรมทำให้แบบ Auto แล้วค่อยมาปรับด้วยมืออีกเล็กน้อย

XSectionPlot_41_topgridelev

4) การจัดรูปตัดตามขวางบนกระดาษ (Page Layout)

  • โมดูลตัวนี้เพิ่งเขียนเสร็จสดๆร้อนๆ การจัดรูปตัดบนกระดาษ โดยที่ผู้ใช้เพียงแต่บอกว่าต้องการรูปตัดในแนวราบ (คอลัมน์) และในแนวดิ่ง (แถว) เท่าไหร่ โปรแกรมก็จะจัดให้ตามต้องการ แต่ที่สำคัญคือความกว้างของรูปตัดจะขึ้นอยู่กับลักษณะงานด้วย เช่นงานถนนอาจความกว้างรูปตัดอาจจะ 100 เมตร ก็แบ่งไปข้างซ้าย 5o เมตร ข้างขวา 50 เมตร ในทางดิ่งก็เหมือนกันสามารถตั้งความสูงในแนวดิ่งได้เช่นกัน นอกจากนี้ระยะระหว่างเส้นกริดในแนวราบและแนวนอนก็ยังสามารถตั้งได้ เช่นบางงานอาจต้องการระยะความห่างเส้นกริดทางราบ 5 เมตร หรือ 10 เมตร ทั้งนี้ขึ้นอยู่กับมาตราส่วนด้วย และความสวยงาม เส้นกริดไม่ถี่มากดูแล้วสบายตา

XSectionPlot_41_sectionlayout2

  • หรือในบางกรณีรูปตัดไม่เต็มหน้า โปรแกรมก็จัดให้ได้

XSectionPlot_41_sectionlayout3

  • บางครั้งผู้ใช้กำหนดระยะห่างไปข้างซ้ายและข้างขวามากเกินและสุดท้ายรูปตัดทับกัน โปรแกรมก็แจ้งเตือนให้ทราบ

XSectionPlot_41_sectionlayout4

สิ่งที่ต้องทำต่อ

1) แสดงรูปตัด (Section Viewer)

  • การแสดงรูปตัดให้ผู้ใช้ได้ดูก่อนเป็นสิ่งที่สำคัญมาก เพราะผู้ใช้จะได้ทราบว่ารูปตัดที่ออกมานั้นหน้าตาเป็นอย่างไร มีส่วนไหนต้องแก้ไขไหม ในส่วนนี้คาดว่าใช้เวลาเขียนโปรแกรมอีกพอสมควร ด้านล่างเป็น Section Viewer ของโปรแกรมรุ่นเก่า เอามาใส่ให้เห็นว่า ที่ออกมาจะประมาณนี้

XSectionPlot_32_SectionViewer1

XSectionPlot_32_SectionViewer2

2) เขียนไฟล์รูปตัดรูปแบบ DXF

  • ส่วนนี้เป็นส่วนที่สำคัญมาก เพราะผู้ใช้ต้องการผลงานในรูปแผนที่แสดงรูปตัดตามขวาง ที่สามารถนำไปเปิดด้วยโปรแกรมด้าน CAD อื่นๆได้เช่น Autocad, Microstation เพื่อสามารถนำไปแก้ไขจนเป็นแผนที่รูปตัดที่พร้อมจะส่งงาน ในส่วนนี้ผมคาดว่าคงใช้เวลาในการโปรแกรมมิ่งไม่มากนัก

XSectionPlot_42_icon_sectionviewer

ก่อนจากกัน

  • สำหรับ Python + Qt เป็นจัดเป็นคู่หู ที่ทรงประสิทธิภาพมาก เป็น Cross-platform ผมใช้ Eclipse ผ่านปลั๊กอิน Pydev เป็นสภาวะแวดล้อมในการพัฒนาโปรแกรม การดีบักหาข้อผิดพลาดของโปรแกรม เนื่องจากคำสั่ง print  ของ Python เก่งมากก็ใช้คำสั่ง print นี้ครับสะดวก รวดเร็ว
  • เมื่อนำไปพัฒนาบน Ubuntu Gnome ก็ออกมาดังที่เห็น

XSectionPlot_41_ubuntugnome

  • หรือนำไปพัฒนาบนแมค ที่ติดเรื่อง font จากปัญหาสระลอย จนต้องมาบังคับฟอนต์ ตามที่อ.จักรนันท์แนะนำ ก็แก้ปัญหานี้ไปได้

XSectionPlot_41_mac

  • พบกันใหม่ครับ

ก้าวต่อไปของโปรแกรมเขียนรูปตัดตามขวาง XSection Plot ในร่างใหม่พัฒนาด้วย Python + Qt

ลาก่อน XSection Plot รุ่น 3.25

  • โปรแกรมพล็อทรูปตัดตามขวาง XSection Plot เดิมๆนั้นผมเขียนด้วย Visual Basic 6 ใช้มาเป็นสิบๆปีแล้ว บัดนี้ถึงกาลเวลาที่จะต้องฝังในความทรงจำแล้ว เพราะฟอนต์ที่เคยแสดงผลได้ในวินโดส์ XP กลับกลายเป็นตัวประหลาดในวินโดส์ 7/8 ผมเข้าใจว่าฟอนต์ระบบที่เคยอยู่ในวินโดส์ XP น่าจะเป็นระบบแบบด็อทเมทริก น่าจะถูกถอดออกไปแล้วในวินโดส์ 7/8 จึงแสดงผลแบบอักขระประหลาดอย่างที่เห็นxsectionplot_old1
  • กลับมารื้อโค๊ดเดิมๆดูคร่าวๆ จำนวนบรรทัดหลายหมื่นบรรทัดทีเดียว ประกอบด้วยโมดูลและคลาสจำนวนมาก ออกอาการงงๆเหมือนกัน เพราะเขียนไว้นานแล้วภาษานี้ก็ไม่ได้เขียนนานแล้ว แต่ด้วยความง่ายของภาษา VB ใช้เวลาไม่นานก็รื้อฟื้น

แรงจูงใจในการนำมาพัฒนาใหม่

  • บอกตรงๆว่าทนเห็นโปรแกรมที่เคยใช้ได้ดีตอนอยู่วินโดส์ XP แต่มาล้มหายตายจากพอมาอยู่บนวินโดส์ 7/8 ไม่ได้ และความรู้สีกอยากช่วยผู้ที่ใช้งานโปรแกรมนี้ ในรุ่นเก่าแล้วต้องการย้ายงานมาบนวินโดส์ 7/8

เครื่องมือและสภาพแวดล้อมในการพัฒนา

  • ในตอนนี้ผมศึกษา Python อยู่ วิธีเดียวที่จะให้โปรแกรมมิงแตกฉาน คือลงมือเขียนโปรแกรม ก่อนหน้านี้ผมเริ่มเขียนด้วย Python เป็นเวอร์ชัน 2.7.5 แบบ 32 บิต ใช้ graphic user interface (GUI) ของ wxWidgets รวมๆเรียกว่า wxPython ตัวโปรแกรมที่ผมเขียนเป็นโปรแกรมด้าน GIS ใช้ Mapnik เป็นตัว  GIS Map Engine เขียนจนจบโปรแกรมนำมาใช้งานได้ดีพอสมควร  สำหรับผมแล้ว wxPython ก็โอเคในระดับหนึ่ง แต่ความรู้สึกส่วนตัวว่าค่อนข้างติดๆขัดๆไม่ลื่นไหล (ทัศนส่วนตัว)
  • ตอนหลังๆมาลองติดตั้ง Python 3.4 แล้วก็ลง Qt5 เรียกว่า PyQt5 แบบ 64 บิต ลองศึกษา Qt5 ควบคู่ไปด้วย ปรากฎว่าอันนี้ใช่เลย ตามสไตล์ของไพธ่อนครับ สั้น กระชับ ฉับไว ทรงพลัง ผมลงทุนไม่ดูโค๊ดเก่าที่เป็นวิชวลเบสิคเก่า ใช้เวลายามว่างๆ ลงมือเขียนใหม่ เกือบทั้งหมด
  • สภาพแวดล้อมในการพัฒนาใช้ Eclipse (ดั้งเดิมเป็น platform สำหรับพัฒนา Java) ในขณะที่เขียนอยู่เป็นรุน Luna ในส่วน Eclipse ติดตั้ง Plug-ins เพิ่มเติมคือตัว PyDev เพื่อให้สามารถใช้ Python บน Eclipse ได้ ทุกอย่างที่กล่าวมาฟรีหมดครับ

pydev_eclipse

XSection Plot โฉมใหม่ ไฉไลกว่าเดิม

  • มาดูหน้าตาโฉมใหม่ก็เรียบง่ายดังที่เห็น

xsectionplot_new1

xsectionplot_new2

ออกแบบไฟล์ข้อมูลใหม่ เป็น XML

  • ไฟล์ข้อมูลเดิมของ XSection Plot  รุ่นเก่าเป็นไฟล์ไบนารี ข้อเสีย คือมนุษย์อ่านไม่ออก ผู้ใช้ไม่สามารถเปิดไฟล์มาดูได้โดยตรง  ข้อเสียในแง่คนพัฒนาโปรแกรมคือ ขยายเพิ่มเติมข้อมูลลำบาก ก็เลยหันมามอง XML ที่เกิดมานมนานเป็นสิบๆปีแล้ว สนใจลองกดปุ่มบวกข้างล่างดูลักษณะไฟล์ข้อมูล แต่รุ่นจริงอาจจะมีการเปลี่ยนแปลง

[code language=”xml” collapse=”true”]
<root version=”1.0″>
<!–Generated by XSection Plot–>
<!–This data file is Existing ground section–>
<Header>
<AppName>XSection Plot</AppName>
<Developer>Prajuab Riabroy</Developer>
<Version>4.0.512</Version>
<SectionType>Ground</SectionType>
<DateCreated>2014-08-07 16:45:56.517701</DateCreated>
</Header>
<ProjectInfo>
<ProjectName>KohKong Coal-Fired Power Plant</ProjectName>
<ClientName>Harbour Department</ClientName>
<ContractorName>Universal Construction Development PCL.</ContractorName>
<DrawingTitle>Cross Section of Access Channel</DrawingTitle>
<DrawnName>DRS</DrawnName>
<ApprovedName>AS</ApprovedName>
<ClientApprovedName>AD</ClientApprovedName>
<CheckedName>PJ</CheckedName>
<SurveyorName>SK</SurveyorName>
<SurveyedDate>1-7/03/2010</SurveyedDate>
<DrawingNo></DrawingNo>
<SheetNo>1/</SheetNo>
<DrawingDate>1-7/03/2010</DrawingDate>
<Revision></Revision>
<UseLocaleLanguage>False</UseLocaleLanguage>
<MapTexts>
<MapText Label=”Client” Locale=”เจ้าของโครงการ”/>
<MapText Label=”Contractor” Locale=”ผู้รับเหมา”/>
<MapText Label=”Drawn” Locale=”เขียน”/>
<MapText Label=”Design” Locale=”ออกแบบ”/>
<MapText Label=”Surveyor” Locale=”สำรวจ”/>
<MapText Label=”Surveyed date” Locale=”วันที่สำรวจ”/>
<MapText Label=”Checked” Locale=”ตรวจสอบ”/>
<MapText Label=”Approved” Locale=”อนุมัติ”/>
<MapText Label=”Client approved” Locale=”ผู้คุมงานอนุมัติ”/>
<MapText Label=”Drawing No.” Locale=”แบบเลขที่”/>
<MapText Label=”Drawing date” Locale=”แบบวันที่”/>
<MapText Label=”Project title” Locale=”โครงการ”/>
<MapText Label=”Drawing title” Locale=”แผนที่แสดง”/>
<MapText Label=”Sheet No.” Locale=”แบบเลขที่”/>
<MapText Label=”Scale” Locale=”มาตราส่วน”/>
<MapText Label=”Vertical” Locale=”ทางดิ่ง”/>
<MapText Label=”Horizontal” Locale=”ทางราบ”/>
<MapText Label=”Vertical scale” Locale=”มาตราส่วนทางดิ่ง”/>
<MapText Label=”Horizontal scale” Locale=”มาตราส่วนทางราบ”/>
<MapText Label=”Legend” Locale=”สัญลักษณ์”/>
<MapText Label=”Notes” Locale=”หมายเหตุ”/>
<MapText Label=”Geodetic Information” Locale=”ข้อมูลทางยีออเดซี”/>
<MapText Label=”No” Locale=”ครั้งที่”/>
<MapText Label=”Amendments” Locale=”ความเห็น”/>
<MapText Label=”By” Locale=”โดย”/>
<MapText Label=”Date” Locale=”วันที่”/>
<MapText Label=”Revision” Locale=”ครั้งที่แก้ไข”/>
</MapTexts>
</ProjectInfo>
<SectionOptions>
<VerticalScale>250.0</VerticalScale>
<HorizontalScale>2500.0</HorizontalScale>
<HozGridSpace>25.0</HozGridSpace>
<VertGridSpace>2.0</VertGridSpace>
<GridLineType>1</GridLineType>
<CalcIntersection>-1</CalcIntersection>
<CalcArea>-1</CalcArea>
<TrimTypical>0</TrimTypical>
<NumDecimalElev>3</NumDecimalElev>
<NumDecimalDist>3</NumDecimalDist>
<UseIntervalText>-1</UseIntervalText>
<IntervalDist>25.0</IntervalDist>
<PrefixText></PrefixText>
<PostfixText></PostfixText>
<UsePostPrefix>0</UsePostPrefix>
<UseOffsetElevFormat>2</UseOffsetElevFormat>
<CalcPlotAreaCut>0</CalcPlotAreaCut>
<CalcPlotAreaFill>0</CalcPlotAreaFill>
<NumVertCLLeft>18</NumVertCLLeft>
<NumVertCLRight>18</NumVertCLRight>
<NumHozTopBottom>8</NumHozTopBottom>
<LeftSideText>LT.</LeftSideText>
<RightSideText>RT.</RightSideText>
<StationText>Km.</StationText>
<SelectedTBlock>3</SelectedTBlock>
<NumSectionRows>6</NumSectionRows>
<NumSectionColumns>2</NumSectionColumns>
<SurveyType>0</SurveyType>
<PlotTBlock>-1</PlotTBlock>
<SwapLeftAndRight>0</SwapLeftAndRight>
</SectionOptions>
<PageSetup>
<PaperSize>A1</PaperSize>
<PageWidth>-999</PageWidth>
<PaperHeight>-999</PaperHeight>
</PageSetup>
<Sections>
<NumSections>12</NumSections>
<Section Name=”2 + 000″>
<XPositionOnPaper>51.66</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>37</NumPoints>
<Points>
<Point Elevation=”-13.75″ Offset=”-450.0″/>
<Point Elevation=”-13.724″ Offset=”-444.229″/>
<Point Elevation=”-13.453″ Offset=”-394.64″/>
<Point Elevation=”-13.708″ Offset=”-357.293″/>
<Point Elevation=”-13.832″ Offset=”-340.678″/>
<Point Elevation=”-13.815″ Offset=”-321.128″/>
<Point Elevation=”-13.812″ Offset=”-303.689″/>
<Point Elevation=”-13.817″ Offset=”-263.4″/>
<Point Elevation=”-13.795″ Offset=”-256.48″/>
<Point Elevation=”-13.774″ Offset=”-237.844″/>
<Point Elevation=”-13.725″ Offset=”-215.868″/>
<Point Elevation=”-13.694″ Offset=”-179.238″/>
<Point Elevation=”-13.694″ Offset=”-146.471″/>
<Point Elevation=”-13.641″ Offset=”-126.792″/>
<Point Elevation=”-13.651″ Offset=”-77.938″/>
<Point Elevation=”-13.577″ Offset=”-68.628″/>
<Point Elevation=”-13.573″ Offset=”-67.035″/>
<Point Elevation=”-13.578″ Offset=”-57.508″/>
<Point Elevation=”-13.545″ Offset=”-14.98″/>
<Point Elevation=”-13.551″ Offset=”-2.388″/>
<Point Elevation=”-13.569″ Offset=”19.95″/>
<Point Elevation=”-13.553″ Offset=”64.032″/>
<Point Elevation=”-13.541″ Offset=”72.772″/>
<Point Elevation=”-13.516″ Offset=”81.096″/>
<Point Elevation=”-13.614″ Offset=”138.343″/>
<Point Elevation=”-13.629″ Offset=”150.271″/>
<Point Elevation=”-13.556″ Offset=”198.013″/>
<Point Elevation=”-13.557″ Offset=”211.934″/>
<Point Elevation=”-13.577″ Offset=”254.983″/>
<Point Elevation=”-13.624″ Offset=”269.121″/>
<Point Elevation=”-13.736″ Offset=”298.975″/>
<Point Elevation=”-13.717″ Offset=”339.278″/>
<Point Elevation=”-13.679″ Offset=”356.86″/>
<Point Elevation=”-13.497″ Offset=”383.249″/>
<Point Elevation=”-13.423″ Offset=”405.804″/>
<Point Elevation=”-13.548″ Offset=”442.413″/>
<Point Elevation=”-13.59″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 050″>
<XPositionOnPaper>42.78</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>37</NumPoints>
<Points>
<Point Elevation=”-13.587″ Offset=”-450.0″/>
<Point Elevation=”-13.564″ Offset=”-431.93″/>
<Point Elevation=”-13.389″ Offset=”-392.416″/>
<Point Elevation=”-13.59″ Offset=”-363.705″/>
<Point Elevation=”-13.714″ Offset=”-349.243″/>
<Point Elevation=”-13.648″ Offset=”-319.939″/>
<Point Elevation=”-13.613″ Offset=”-305.319″/>
<Point Elevation=”-13.836″ Offset=”-245.696″/>
<Point Elevation=”-13.833″ Offset=”-244.203″/>
<Point Elevation=”-13.656″ Offset=”-200.692″/>
<Point Elevation=”-13.608″ Offset=”-176.25″/>
<Point Elevation=”-13.525″ Offset=”-143.894″/>
<Point Elevation=”-13.552″ Offset=”-129.046″/>
<Point Elevation=”-13.527″ Offset=”-96.253″/>
<Point Elevation=”-13.656″ Offset=”-54.905″/>
<Point Elevation=”-13.654″ Offset=”-53.835″/>
<Point Elevation=”-13.566″ Offset=”-45.158″/>
<Point Elevation=”-13.591″ Offset=”-20.343″/>
<Point Elevation=”-13.567″ Offset=”-0.305″/>
<Point Elevation=”-13.438″ Offset=”26.667″/>
<Point Elevation=”-13.397″ Offset=”59.524″/>
<Point Elevation=”-13.375″ Offset=”70.237″/>
<Point Elevation=”-13.38″ Offset=”77.772″/>
<Point Elevation=”-13.49″ Offset=”115.256″/>
<Point Elevation=”-13.6″ Offset=”156.486″/>
<Point Elevation=”-13.475″ Offset=”180.904″/>
<Point Elevation=”-13.453″ Offset=”188.472″/>
<Point Elevation=”-13.454″ Offset=”203.878″/>
<Point Elevation=”-13.471″ Offset=”256.873″/>
<Point Elevation=”-13.554″ Offset=”276.218″/>
<Point Elevation=”-13.604″ Offset=”294.693″/>
<Point Elevation=”-13.63″ Offset=”328.866″/>
<Point Elevation=”-13.63″ Offset=”355.449″/>
<Point Elevation=”-13.502″ Offset=”374.332″/>
<Point Elevation=”-13.316″ Offset=”406.895″/>
<Point Elevation=”-13.486″ Offset=”441.986″/>
<Point Elevation=”-13.52″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 100″>
<XPositionOnPaper>33.89</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>38</NumPoints>
<Points>
<Point Elevation=”-13.478″ Offset=”-450.0″/>
<Point Elevation=”-13.468″ Offset=”-424.977″/>
<Point Elevation=”-13.401″ Offset=”-404.422″/>
<Point Elevation=”-13.463″ Offset=”-387.405″/>
<Point Elevation=”-13.633″ Offset=”-344.262″/>
<Point Elevation=”-13.608″ Offset=”-339.036″/>
<Point Elevation=”-13.494″ Offset=”-305.986″/>
<Point Elevation=”-13.552″ Offset=”-274.196″/>
<Point Elevation=”-13.555″ Offset=”-259.655″/>
<Point Elevation=”-13.555″ Offset=”-237.91″/>
<Point Elevation=”-13.486″ Offset=”-192.39″/>
<Point Elevation=”-13.35″ Offset=”-155.4″/>
<Point Elevation=”-13.323″ Offset=”-145.491″/>
<Point Elevation=”-13.288″ Offset=”-86.808″/>
<Point Elevation=”-13.275″ Offset=”-58.314″/>
<Point Elevation=”-13.286″ Offset=”-56.712″/>
<Point Elevation=”-13.511″ Offset=”-44.626″/>
<Point Elevation=”-13.477″ Offset=”-19.724″/>
<Point Elevation=”-13.435″ Offset=”-1.021″/>
<Point Elevation=”-13.385″ Offset=”20.078″/>
<Point Elevation=”-13.354″ Offset=”48.949″/>
<Point Elevation=”-13.322″ Offset=”58.859″/>
<Point Elevation=”-13.309″ Offset=”73.917″/>
<Point Elevation=”-13.431″ Offset=”135.063″/>
<Point Elevation=”-13.512″ Offset=”161.041″/>
<Point Elevation=”-13.36″ Offset=”170.629″/>
<Point Elevation=”-13.285″ Offset=”176.557″/>
<Point Elevation=”-13.303″ Offset=”252.984″/>
<Point Elevation=”-13.309″ Offset=”261.078″/>
<Point Elevation=”-13.349″ Offset=”269.352″/>
<Point Elevation=”-13.442″ Offset=”286.219″/>
<Point Elevation=”-13.451″ Offset=”289.018″/>
<Point Elevation=”-13.454″ Offset=”295.508″/>
<Point Elevation=”-13.381″ Offset=”357.63″/>
<Point Elevation=”-13.291″ Offset=”397.562″/>
<Point Elevation=”-13.265″ Offset=”410.885″/>
<Point Elevation=”-13.291″ Offset=”415.854″/>
<Point Elevation=”-13.436″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 150″>
<XPositionOnPaper>25.01</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>36</NumPoints>
<Points>
<Point Elevation=”-13.42″ Offset=”-450.0″/>
<Point Elevation=”-13.422″ Offset=”-448.815″/>
<Point Elevation=”-13.36″ Offset=”-407.76″/>
<Point Elevation=”-13.421″ Offset=”-383.018″/>
<Point Elevation=”-13.466″ Offset=”-343.431″/>
<Point Elevation=”-13.477″ Offset=”-330.434″/>
<Point Elevation=”-13.426″ Offset=”-308.725″/>
<Point Elevation=”-13.339″ Offset=”-248.217″/>
<Point Elevation=”-13.318″ Offset=”-227.42″/>
<Point Elevation=”-13.346″ Offset=”-185.203″/>
<Point Elevation=”-13.22″ Offset=”-153.307″/>
<Point Elevation=”-13.204″ Offset=”-149.805″/>
<Point Elevation=”-13.299″ Offset=”-109.743″/>
<Point Elevation=”-13.355″ Offset=”-76.976″/>
<Point Elevation=”-13.291″ Offset=”-70.628″/>
<Point Elevation=”-13.2″ Offset=”-57.454″/>
<Point Elevation=”-13.217″ Offset=”-40.497″/>
<Point Elevation=”-13.27″ Offset=”-5.497″/>
<Point Elevation=”-13.298″ Offset=”42.337″/>
<Point Elevation=”-13.313″ Offset=”57.83″/>
<Point Elevation=”-13.306″ Offset=”58.86″/>
<Point Elevation=”-13.119″ Offset=”66.396″/>
<Point Elevation=”-13.147″ Offset=”99.813″/>
<Point Elevation=”-13.307″ Offset=”160.186″/>
<Point Elevation=”-13.248″ Offset=”169.925″/>
<Point Elevation=”-13.135″ Offset=”176.817″/>
<Point Elevation=”-13.233″ Offset=”220.607″/>
<Point Elevation=”-13.302″ Offset=”256.981″/>
<Point Elevation=”-13.362″ Offset=”268.584″/>
<Point Elevation=”-13.398″ Offset=”285.574″/>
<Point Elevation=”-13.254″ Offset=”344.07″/>
<Point Elevation=”-13.228″ Offset=”357.109″/>
<Point Elevation=”-13.214″ Offset=”363.701″/>
<Point Elevation=”-13.071″ Offset=”408.839″/>
<Point Elevation=”-13.195″ Offset=”427.653″/>
<Point Elevation=”-13.336″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 200″>
<XPositionOnPaper>16.13</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>34</NumPoints>
<Points>
<Point Elevation=”-13.299″ Offset=”-450.0″/>
<Point Elevation=”-13.244″ Offset=”-436.225″/>
<Point Elevation=”-13.11″ Offset=”-404.037″/>
<Point Elevation=”-13.219″ Offset=”-363.07″/>
<Point Elevation=”-13.268″ Offset=”-346.98″/>
<Point Elevation=”-13.241″ Offset=”-317.712″/>
<Point Elevation=”-13.227″ Offset=”-310.438″/>
<Point Elevation=”-13.238″ Offset=”-280.224″/>
<Point Elevation=”-13.258″ Offset=”-255.553″/>
<Point Elevation=”-13.299″ Offset=”-211.447″/>
<Point Elevation=”-13.297″ Offset=”-203.957″/>
<Point Elevation=”-13.325″ Offset=”-143.444″/>
<Point Elevation=”-13.182″ Offset=”-96.867″/>
<Point Elevation=”-13.167″ Offset=”-71.842″/>
<Point Elevation=”-13.173″ Offset=”-66.961″/>
<Point Elevation=”-13.161″ Offset=”-63.511″/>
<Point Elevation=”-13.105″ Offset=”-25.008″/>
<Point Elevation=”-13.095″ Offset=”-1.821″/>
<Point Elevation=”-13.263″ Offset=”45.687″/>
<Point Elevation=”-13.267″ Offset=”49.066″/>
<Point Elevation=”-13.264″ Offset=”49.467″/>
<Point Elevation=”-13.074″ Offset=”66.884″/>
<Point Elevation=”-13.11″ Offset=”161.015″/>
<Point Elevation=”-13.061″ Offset=”165.896″/>
<Point Elevation=”-13.022″ Offset=”174.39″/>
<Point Elevation=”-13.136″ Offset=”236.021″/>
<Point Elevation=”-13.157″ Offset=”249.355″/>
<Point Elevation=”-13.194″ Offset=”259.713″/>
<Point Elevation=”-13.265″ Offset=”285.85″/>
<Point Elevation=”-13.251″ Offset=”296.83″/>
<Point Elevation=”-13.145″ Offset=”348.582″/>
<Point Elevation=”-13.023″ Offset=”386.699″/>
<Point Elevation=”-12.918″ Offset=”410.555″/>
<Point Elevation=”-13.122″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 250″>
<XPositionOnPaper>7.24</XPositionOnPaper>
<YPositionOnPaper>21.18</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>35</NumPoints>
<Points>
<Point Elevation=”-13.141″ Offset=”-450.0″/>
<Point Elevation=”-13.135″ Offset=”-427.089″/>
<Point Elevation=”-13.116″ Offset=”-408.612″/>
<Point Elevation=”-13.158″ Offset=”-378.649″/>
<Point Elevation=”-13.208″ Offset=”-347.778″/>
<Point Elevation=”-13.103″ Offset=”-300.077″/>
<Point Elevation=”-13.082″ Offset=”-258.336″/>
<Point Elevation=”-13.043″ Offset=”-232.005″/>
<Point Elevation=”-13.019″ Offset=”-212.37″/>
<Point Elevation=”-13.039″ Offset=”-193.983″/>
<Point Elevation=”-13.092″ Offset=”-151.651″/>
<Point Elevation=”-13.105″ Offset=”-143.345″/>
<Point Elevation=”-13.093″ Offset=”-121.002″/>
<Point Elevation=”-13.082″ Offset=”-74.012″/>
<Point Elevation=”-13.072″ Offset=”-70.704″/>
<Point Elevation=”-12.939″ Offset=”-61.159″/>
<Point Elevation=”-12.878″ Offset=”-54.908″/>
<Point Elevation=”-13.036″ Offset=”-16.297″/>
<Point Elevation=”-12.959″ Offset=”13.485″/>
<Point Elevation=”-13.014″ Offset=”34.515″/>
<Point Elevation=”-13.016″ Offset=”41.274″/>
<Point Elevation=”-12.881″ Offset=”73.456″/>
<Point Elevation=”-12.997″ Offset=”132.919″/>
<Point Elevation=”-13.083″ Offset=”160.722″/>
<Point Elevation=”-12.971″ Offset=”167.25″/>
<Point Elevation=”-12.897″ Offset=”174.705″/>
<Point Elevation=”-12.973″ Offset=”224.215″/>
<Point Elevation=”-13.039″ Offset=”247.5″/>
<Point Elevation=”-12.997″ Offset=”275.574″/>
<Point Elevation=”-12.965″ Offset=”306.485″/>
<Point Elevation=”-12.954″ Offset=”335.878″/>
<Point Elevation=”-12.932″ Offset=”347.275″/>
<Point Elevation=”-12.763″ Offset=”408.665″/>
<Point Elevation=”-12.844″ Offset=”430.348″/>
<Point Elevation=”-12.93″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 300″>
<XPositionOnPaper>51.66</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>34</NumPoints>
<Points>
<Point Elevation=”-13.074″ Offset=”-450.0″/>
<Point Elevation=”-12.945″ Offset=”-417.015″/>
<Point Elevation=”-12.941″ Offset=”-415.185″/>
<Point Elevation=”-13.024″ Offset=”-341.605″/>
<Point Elevation=”-13.123″ Offset=”-294.567″/>
<Point Elevation=”-13.107″ Offset=”-279.439″/>
<Point Elevation=”-13.068″ Offset=”-253.199″/>
<Point Elevation=”-13.027″ Offset=”-222.165″/>
<Point Elevation=”-13.007″ Offset=”-210.839″/>
<Point Elevation=”-12.978″ Offset=”-193.209″/>
<Point Elevation=”-12.874″ Offset=”-146.031″/>
<Point Elevation=”-12.947″ Offset=”-112.293″/>
<Point Elevation=”-12.994″ Offset=”-74.731″/>
<Point Elevation=”-12.951″ Offset=”-56.842″/>
<Point Elevation=”-12.941″ Offset=”-51.044″/>
<Point Elevation=”-12.928″ Offset=”-49.349″/>
<Point Elevation=”-12.919″ Offset=”-41.744″/>
<Point Elevation=”-12.857″ Offset=”-17.072″/>
<Point Elevation=”-12.849″ Offset=”-1.213″/>
<Point Elevation=”-12.937″ Offset=”29.996″/>
<Point Elevation=”-12.943″ Offset=”43.79″/>
<Point Elevation=”-12.822″ Offset=”75.413″/>
<Point Elevation=”-12.792″ Offset=”80.941″/>
<Point Elevation=”-12.826″ Offset=”90.04″/>
<Point Elevation=”-13.069″ Offset=”164.519″/>
<Point Elevation=”-12.837″ Offset=”183.079″/>
<Point Elevation=”-12.873″ Offset=”222.341″/>
<Point Elevation=”-12.893″ Offset=”250.522″/>
<Point Elevation=”-12.863″ Offset=”272.959″/>
<Point Elevation=”-12.84″ Offset=”289.627″/>
<Point Elevation=”-12.791″ Offset=”341.037″/>
<Point Elevation=”-12.791″ Offset=”375.553″/>
<Point Elevation=”-12.778″ Offset=”402.959″/>
<Point Elevation=”-12.782″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 350″>
<XPositionOnPaper>42.78</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>35</NumPoints>
<Points>
<Point Elevation=”-12.783″ Offset=”-450.0″/>
<Point Elevation=”-12.807″ Offset=”-423.664″/>
<Point Elevation=”-12.842″ Offset=”-402.422″/>
<Point Elevation=”-12.834″ Offset=”-393.811″/>
<Point Elevation=”-12.879″ Offset=”-350.323″/>
<Point Elevation=”-12.966″ Offset=”-318.938″/>
<Point Elevation=”-13.011″ Offset=”-304.688″/>
<Point Elevation=”-12.898″ Offset=”-262.751″/>
<Point Elevation=”-12.866″ Offset=”-244.765″/>
<Point Elevation=”-12.876″ Offset=”-206.942″/>
<Point Elevation=”-12.88″ Offset=”-204.564″/>
<Point Elevation=”-12.856″ Offset=”-158.672″/>
<Point Elevation=”-12.786″ Offset=”-141.587″/>
<Point Elevation=”-12.787″ Offset=”-103.149″/>
<Point Elevation=”-12.766″ Offset=”-91.41″/>
<Point Elevation=”-12.779″ Offset=”-82.894″/>
<Point Elevation=”-12.781″ Offset=”-78.804″/>
<Point Elevation=”-12.767″ Offset=”-58.384″/>
<Point Elevation=”-12.765″ Offset=”-39.14″/>
<Point Elevation=”-12.676″ Offset=”-10.552″/>
<Point Elevation=”-12.786″ Offset=”13.725″/>
<Point Elevation=”-12.924″ Offset=”42.861″/>
<Point Elevation=”-12.794″ Offset=”60.497″/>
<Point Elevation=”-12.654″ Offset=”81.948″/>
<Point Elevation=”-12.833″ Offset=”123.769″/>
<Point Elevation=”-12.916″ Offset=”163.182″/>
<Point Elevation=”-12.784″ Offset=”181.207″/>
<Point Elevation=”-12.765″ Offset=”188.92″/>
<Point Elevation=”-12.792″ Offset=”255.693″/>
<Point Elevation=”-12.815″ Offset=”295.177″/>
<Point Elevation=”-12.79″ Offset=”315.292″/>
<Point Elevation=”-12.749″ Offset=”345.204″/>
<Point Elevation=”-12.744″ Offset=”364.207″/>
<Point Elevation=”-12.753″ Offset=”396.564″/>
<Point Elevation=”-12.68″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 400″>
<XPositionOnPaper>33.89</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>36</NumPoints>
<Points>
<Point Elevation=”-12.776″ Offset=”-450.0″/>
<Point Elevation=”-12.779″ Offset=”-447.698″/>
<Point Elevation=”-12.774″ Offset=”-434.746″/>
<Point Elevation=”-12.716″ Offset=”-414.286″/>
<Point Elevation=”-12.81″ Offset=”-359.948″/>
<Point Elevation=”-12.835″ Offset=”-352.389″/>
<Point Elevation=”-12.834″ Offset=”-346.592″/>
<Point Elevation=”-12.907″ Offset=”-315.681″/>
<Point Elevation=”-12.863″ Offset=”-285.783″/>
<Point Elevation=”-12.746″ Offset=”-240.545″/>
<Point Elevation=”-12.748″ Offset=”-212.21″/>
<Point Elevation=”-12.82″ Offset=”-189.251″/>
<Point Elevation=”-12.715″ Offset=”-173.517″/>
<Point Elevation=”-12.632″ Offset=”-145.846″/>
<Point Elevation=”-12.591″ Offset=”-106.965″/>
<Point Elevation=”-12.571″ Offset=”-91.109″/>
<Point Elevation=”-12.571″ Offset=”-48.284″/>
<Point Elevation=”-12.577″ Offset=”-45.561″/>
<Point Elevation=”-12.599″ Offset=”-14.411″/>
<Point Elevation=”-12.787″ Offset=”15.841″/>
<Point Elevation=”-12.859″ Offset=”44.469″/>
<Point Elevation=”-12.629″ Offset=”73.752″/>
<Point Elevation=”-12.541″ Offset=”85.843″/>
<Point Elevation=”-12.606″ Offset=”127.261″/>
<Point Elevation=”-12.689″ Offset=”164.56″/>
<Point Elevation=”-12.777″ Offset=”188.004″/>
<Point Elevation=”-12.794″ Offset=”194.282″/>
<Point Elevation=”-12.765″ Offset=”220.036″/>
<Point Elevation=”-12.697″ Offset=”257.027″/>
<Point Elevation=”-12.742″ Offset=”298.261″/>
<Point Elevation=”-12.761″ Offset=”308.727″/>
<Point Elevation=”-12.739″ Offset=”317.584″/>
<Point Elevation=”-12.692″ Offset=”346.633″/>
<Point Elevation=”-12.663″ Offset=”389.2″/>
<Point Elevation=”-12.662″ Offset=”396.811″/>
<Point Elevation=”-12.622″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 450″>
<XPositionOnPaper>25.01</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>35</NumPoints>
<Points>
<Point Elevation=”-12.772″ Offset=”-450.0″/>
<Point Elevation=”-12.779″ Offset=”-447.895″/>
<Point Elevation=”-12.728″ Offset=”-435.359″/>
<Point Elevation=”-12.625″ Offset=”-419.964″/>
<Point Elevation=”-12.671″ Offset=”-361.947″/>
<Point Elevation=”-12.65″ Offset=”-349.652″/>
<Point Elevation=”-12.745″ Offset=”-323.285″/>
<Point Elevation=”-12.881″ Offset=”-302.461″/>
<Point Elevation=”-12.774″ Offset=”-273.658″/>
<Point Elevation=”-12.614″ Offset=”-241.564″/>
<Point Elevation=”-12.923″ Offset=”-198.167″/>
<Point Elevation=”-12.751″ Offset=”-169.044″/>
<Point Elevation=”-12.581″ Offset=”-144.88″/>
<Point Elevation=”-12.616″ Offset=”-115.712″/>
<Point Elevation=”-12.607″ Offset=”-103.85″/>
<Point Elevation=”-12.527″ Offset=”-58.007″/>
<Point Elevation=”-12.522″ Offset=”-49.404″/>
<Point Elevation=”-12.511″ Offset=”-40.605″/>
<Point Elevation=”-12.526″ Offset=”-7.582″/>
<Point Elevation=”-12.637″ Offset=”44.494″/>
<Point Elevation=”-12.638″ Offset=”46.028″/>
<Point Elevation=”-12.336″ Offset=”98.718″/>
<Point Elevation=”-12.345″ Offset=”101.193″/>
<Point Elevation=”-12.764″ Offset=”162.39″/>
<Point Elevation=”-12.66″ Offset=”204.444″/>
<Point Elevation=”-12.627″ Offset=”229.054″/>
<Point Elevation=”-12.548″ Offset=”258.494″/>
<Point Elevation=”-12.644″ Offset=”293.019″/>
<Point Elevation=”-12.669″ Offset=”307.749″/>
<Point Elevation=”-12.618″ Offset=”321.733″/>
<Point Elevation=”-12.477″ Offset=”351.792″/>
<Point Elevation=”-12.529″ Offset=”375.735″/>
<Point Elevation=”-12.545″ Offset=”385.798″/>
<Point Elevation=”-12.574″ Offset=”415.152″/>
<Point Elevation=”-12.621″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 500″>
<XPositionOnPaper>16.13</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>37</NumPoints>
<Points>
<Point Elevation=”-12.675″ Offset=”-450.0″/>
<Point Elevation=”-12.682″ Offset=”-448.083″/>
<Point Elevation=”-12.672″ Offset=”-428.342″/>
<Point Elevation=”-12.68″ Offset=”-425.205″/>
<Point Elevation=”-12.649″ Offset=”-408.657″/>
<Point Elevation=”-12.507″ Offset=”-340.077″/>
<Point Elevation=”-12.62″ Offset=”-320.546″/>
<Point Elevation=”-12.737″ Offset=”-293.581″/>
<Point Elevation=”-12.521″ Offset=”-251.753″/>
<Point Elevation=”-12.513″ Offset=”-249.937″/>
<Point Elevation=”-12.801″ Offset=”-216.359″/>
<Point Elevation=”-12.653″ Offset=”-174.972″/>
<Point Elevation=”-12.591″ Offset=”-158.425″/>
<Point Elevation=”-12.541″ Offset=”-144.303″/>
<Point Elevation=”-12.614″ Offset=”-115.444″/>
<Point Elevation=”-12.615″ Offset=”-111.726″/>
<Point Elevation=”-12.6″ Offset=”-104.058″/>
<Point Elevation=”-12.415″ Offset=”-42.364″/>
<Point Elevation=”-12.406″ Offset=”-28.724″/>
<Point Elevation=”-12.416″ Offset=”-1.475″/>
<Point Elevation=”-12.36″ Offset=”38.494″/>
<Point Elevation=”-12.353″ Offset=”52.28″/>
<Point Elevation=”-12.321″ Offset=”63.68″/>
<Point Elevation=”-12.198″ Offset=”112.168″/>
<Point Elevation=”-12.383″ Offset=”139.067″/>
<Point Elevation=”-12.577″ Offset=”161.059″/>
<Point Elevation=”-12.541″ Offset=”188.84″/>
<Point Elevation=”-12.454″ Offset=”213.082″/>
<Point Elevation=”-12.385″ Offset=”256.777″/>
<Point Elevation=”-12.375″ Offset=”261.779″/>
<Point Elevation=”-12.415″ Offset=”269.668″/>
<Point Elevation=”-12.639″ Offset=”309.342″/>
<Point Elevation=”-12.483″ Offset=”329.784″/>
<Point Elevation=”-12.337″ Offset=”352.33″/>
<Point Elevation=”-12.37″ Offset=”365.049″/>
<Point Elevation=”-12.507″ Offset=”423.352″/>
<Point Elevation=”-12.597″ Offset=”450.0″/>
</Points>
</Section>
<Section Name=”2 + 550″>
<XPositionOnPaper>7.24</XPositionOnPaper>
<YPositionOnPaper>62.03</YPositionOnPaper>
<TopGridElev>0.0</TopGridElev>
<NumPoints>40</NumPoints>
<Points>
<Point Elevation=”-12.351″ Offset=”-450.0″/>
<Point Elevation=”-12.346″ Offset=”-440.604″/>
<Point Elevation=”-12.456″ Offset=”-420.924″/>
<Point Elevation=”-12.422″ Offset=”-356.665″/>
<Point Elevation=”-12.436″ Offset=”-351.476″/>
<Point Elevation=”-12.433″ Offset=”-348.926″/>
<Point Elevation=”-12.599″ Offset=”-305.043″/>
<Point Elevation=”-12.617″ Offset=”-296.42″/>
<Point Elevation=”-12.516″ Offset=”-254.712″/>
<Point Elevation=”-12.489″ Offset=”-238.476″/>
<Point Elevation=”-12.648″ Offset=”-206.205″/>
<Point Elevation=”-12.563″ Offset=”-187.983″/>
<Point Elevation=”-12.407″ Offset=”-147.195″/>
<Point Elevation=”-12.453″ Offset=”-122.034″/>
<Point Elevation=”-12.46″ Offset=”-116.462″/>
<Point Elevation=”-12.465″ Offset=”-115.775″/>
<Point Elevation=”-12.453″ Offset=”-110.426″/>
<Point Elevation=”-12.39″ Offset=”-62.05″/>
<Point Elevation=”-12.294″ Offset=”-40.738″/>
<Point Elevation=”-12.318″ Offset=”-24.44″/>
<Point Elevation=”-12.277″ Offset=”-5.967″/>
<Point Elevation=”-12.467″ Offset=”31.391″/>
<Point Elevation=”-12.537″ Offset=”53.35″/>
<Point Elevation=”-12.432″ Offset=”77.607″/>
<Point Elevation=”-12.187″ Offset=”125.999″/>
<Point Elevation=”-12.356″ Offset=”151.552″/>
<Point Elevation=”-12.421″ Offset=”160.797″/>
<Point Elevation=”-12.325″ Offset=”199.432″/>
<Point Elevation=”-12.286″ Offset=”218.382″/>
<Point Elevation=”-12.281″ Offset=”225.988″/>
<Point Elevation=”-12.28″ Offset=”255.015″/>
<Point Elevation=”-12.398″ Offset=”283.83″/>
<Point Elevation=”-12.476″ Offset=”305.13″/>
<Point Elevation=”-12.396″ Offset=”329.028″/>
<Point Elevation=”-12.277″ Offset=”354.738″/>
<Point Elevation=”-12.291″ Offset=”372.85″/>
<Point Elevation=”-12.291″ Offset=”400.408″/>
<Point Elevation=”-12.334″ Offset=”421.647″/>
<Point Elevation=”-12.426″ Offset=”448.515″/>
<Point Elevation=”-12.419″ Offset=”450.0″/>
</Points>
</Section>
</Sections>
</root>

[/code]

ระยะเวลาในการพัฒนา

  • เนื่องจากโปรแกรมไม่ได้ถูกพัฒนาในเชิงพานิชย์ และผมใช้เวลาว่างเป็นงานอดิเรก จึงไม่สามารถกำหนดเวลาที่แน่นอนได้ แต่คาดว่าอย่างน้อยอีกสักครึ่งปี ถึงจะได้ยลโฉมกัน ลองไปรันในลีนุกซ์ก็ไม่มีปัญหา ใช้ดีเหมือนบนวินโดส์ แต่ไปลองบนแมค ภาษาไทยกลับเกิดอาการสระลอย ยังหาวิธีแก้ไขไม่ได้ แต่ยังมีเวลาอีกนานครับ