Category: python

Update : โปรแกรม Surveyor Pocket Tools คำนวณความสูงจีออยด์ TGM2017

Update : โปรแกรม Surveyor Pocket Tools คำนวณความสูงจีออยด์ TGM2017

มาตามสัญญาที่ผมบอกว่าจะอัพเดท Surveyor Pocket Tools โปรแกรมช่างสำรวจฉบับกระเป๋า ให้สามารถใช้งานคำนวณความสูงจีออยด์ TGM2017 (Thailand Precise Geoid Model 2017) ดั้งเดิมสามารถคำนวณบนโมเดล EGM96 และ EGM2008

เปลี่ยนวีธีการคำนวณโดยใช้ไลบรารี Proj4

ดั้งเดิมตอนคำนวณหาความสูงจีออยด์บน EGM96 และ EGM2008 ผมพัฒนาโค้ดโปรแกรมมาจากการดัดแปลงของโค้ดดั้งเดิมภาษาซีของผู้พัฒนาท่านอื่นมาเป็นภาษาไพทอน ขณะนี้ได้ยกเลิกโค้ดชุดนี้หันมาใช้ไลบรารี Proj4 คำนวณให้ทั้งหมดเพราะสะดวกมาก และไลบรารี Proj4 สามารถจัดการเมมโมรีได้ดีกว่าของผมมาก สังเกตก่อนหน้านี้ถ้าใครเข้าไปดูใน Task Manager ของวินโดส์อาจจะตกใจที่โปรแกรม Surveyor Pocket Tools ของผมเขมือบเมมโมรีมหาศาล เพราะผมใช้วิธีอ่านไฟล์ของโมเดล EGM96 และ EGM2008 มาเก็บไว้ในเมมโมรีตอนเปิดโปรแกรม ถ้าใครใช้เครื่องคอมเก่าๆ อาจจะรำคาญตอนเปิดโปรแกรมเพราะรอนานมาก ขณะนี้ปัญหาเหล่านั้นได้หมดไป

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

ตอนนี้ผมได้ปรับมาเป็นรุ่น 1.02 build 632 ก็ไปดาวน์โหลดกันได้ที่ลิ๊งค์นี้ มีทั้งสองเวอร์ชั่นให้เลือกคือ Windows 32 บิตและ Windows 64 บิต

Surveyor Pocket Tools V1.02 build 632

ตอนคำนวณความสูงจีออยด์ ไลบรารี Proj4 จะเปิดไฟล์โมเดลมาอ่านคำนวณหาความสูงจีออยด์ ซึ่งทำได้รวดเร็วมาก และโปรแกรม Surveyor Pocket Tools ในปัจจุบันก็กินเมมโมรีลงน้อยมาก ขณะเปิดโปรแกรมมายังไม่ได้คำนวณอะไรกินเมมโมรีประมาณ 95 MB ตอนคำนวณความสูงจีออยด์กินไปประมาณ 250 MB ก็ไม่มากมาย

โมเดล EGM96, EGM2008 และ TGM2017 ถ้าสังเกตุตอนติดตั้งโปรแกรมจะเห็นชื่อไฟล์คือ egm96_15.gtx, egm08_25.gtx และ tgm2017.gtx ไฟล์หลังสุดนี้ผมดัดแปลงจากไฟล์แอสกี้ของกรมแผนที่ทหารเป็นรูปแบบมาตรฐานของ NOAA ที่ไลบรารีต้องการ

ไฟล์โมเดลของจีออยด์หลังจากติดตั้งโปรแกรม

คำนวณความสูงจีออยด์

คลิกที่โปรแกรมย่อย “Geoid Height” จะเห็นไดอะล็อกหน้าตาดังรูปด้านล่าง ป้อนค่า latitude, longitude ไปดังรูป คลิกลูกศรเพื่อทำการคำนวณจะได้ความสูงจีออยด์

ทดสอบคำนวณความสูงจีออยด์จุดที่ 1

ลองปักหมุด Google Maps ดู

ปักหมุดบน Google Maps

มาทดสอบจุดที่ 2 กันต่อ ป้อน latitude, longitude เข้าไปดังนี้

ทดสอบคำนวณจุดที่ 2

สิ่งที่จะพัฒนาต่อไป

ตอนนี้คำนวณแบบแมนวลจุดต่อจุด อนาคตผมจะให้สามารถเปิดไฟล์มาคำนวณได้เพื่อความสะดวก ส่วนโปรแกรมย่อยอื่นที่ใช้คำนวณหาความสูงจีออยด์เช่น Point Scale Factor, Line Scale Factor ยังใช้จีออยด์ EGM2008 เป็นค่าปริยาย ผมจะออกแบบพัฒนาโปรแกรมให้ผู้ใช้สามารถเลือกโมเดลจีออยด์ได้ ติดตามกันต่อไปครับ

ทดสอบคำนวณหาความสูงจีออยด์ TGM2017 ด้วยไลบรารี Proj.4

ทดสอบคำนวณหาความสูงจีออยด์ TGM2017 ด้วยไลบรารี Proj.4

ไม่นานมานี้มีผมดาวน์โหลดไฟล์โปรแกรมและข้อมูลของ TGM2017 เรียกเต็มๆคือ Thailand Geoid Model 2017 ที่เป็นโครงการร่วมมือจากหลายๆฝ่ายของทางราชการ ผมยังไม่มีโอกาสได้นำไปใช้งาน โดยเฉพาะจะนำมาประยุกต์ใช้ในงานรังวัด GNSS ถือว่าเป็นสิ่งที่พวกเรารอคอยมานานที่จะได้มี local geoid model มาใช้งานกัน

โดยเฉพาะงานรังวัด GNSS เมื่อคำนวณแล้วจะได้ค่าพิกัดทางราบ และทางดิ่งจะได้ความสูง (h – Ellipsoid height) ที่เทียบกับทรงรี WGS84 ถ้าเรามีแบบจำลองความสูงจีออยด์สามารถคำนวณหาความสูง (H – Orthometric height) หรือระดับน้ำทะเลปานกลางได้ จากสูตร h = H + N โดยที่ไม่ต้องมีการเดินระดับไปหาจุดที่รังวัด GNSS แต่อย่างใด โดยที่โปรแกรมคำนวณหาค่าความสูงจีออยด์ (N – Geoid Height หรือ Geoid Separation) ที่ผมจะมาลองเขียนโปรแกรมเล็กๆเพื่อหาค่า N นี้ด้วยภาษาไพทอนในลำดับต่อไป

ในตอนนี้ผมจะใช้ไลบรารี Proj.4 เรียกไลบรารีในเวอร์ชั่นภาษาไพทอนนี้ว่า PyProj ตัวไลบรารี Proj.4 เองก็เพิ่งสนับสนุนการคำนวณ Vertical Datum ไปไม่นานนี้เอง สมมติว่าถ้าไม่ได้ใช้ไลบรารีตัวนี้เป็นตัวช่วยคงเป็นงานหนักหนาสาหัสเหมือนกัน

แปลงรูปแบบไฟล์ของแบบจำลองความสูงจีออยด์ TGM2017

ไฟล์แบบจำลองความสูงจีออยด์ TGM2017 ที่ผมดาวน์โหลดได้มานั้นเป็นแอสกี้ (Text file) ขนาดประมาณ 10.7 MB และมีโปรแกรมคำนวณมาด้วยชื่อไฟล์ execute คือ “TGM2017.EXE” สำหรับไฟล์แอสกี้ในคู่มือแสดงรายละเอียดดังนี้

  • ค่าละติจูด (Latitude) เริ่มต้น : 3 องศา
  • ค่าลองจิจูด (Longitude) เริ่มต้น : 95 องศา
  • ความละเอียดเชิงพื้นที่ : 1 ลิปดา
  • จานวนหลัก (Column) : 780 หลัก
  • จานวนแถว (Row) : 1200 แถว
TGM2017 layout

สำหรับไลบรารี PyProj นั้นถ้าต้องการคำนวณ Vertical Datum ต้องการไฟล์ในรูปแบบ gtx ที่กำหนดมาตรฐานโดย NOAA เพื่อให้การใช้งานมีประสิทธิภาพมาก ทางนี้แนะนำให้ใช้ไฟล์ไบนารีเพื่อประหยัดเมมโมรี สำหรับรูปแบบไฟล์ gtx นั้นไม่มีอะไรมาก กำหนดให้มีไฟล์ header เพื่อบอกจุดเริ่มต้นขอบเขตการใช้งาน ระยะห่างของละติจูดและลองจิจูดของแต่ละจุดในแนวตั้งและแนวนอน จำนวนแถวและจำนวนคอลัมน์ ที่เหลือจะเป็นความสูงจีออยด์ในแต่ละจุดมีขนาดเท่ากับ จำนวณแถวคูณจำนวนคอลัมน์ ตัวหัวไฟล์ผมสามารถเขียนได้ดังนี้

3.0 95.0 0.01666666666 0.01666666666 1200 780

อธิบาย : ละติจูดของมุมล่างซ้าย ลองจิจูดของมุมล่างซ้าย ระยะห่างของจุดในแนวละติจูด ระยะห่างของจุดในแนวลองจิจูด จำนวนแถว จำนวนคอลัมน์

ผมตรวจสอบไฟล์แอสกี้แล้วพบว่า ความสูงจีออยด์ถูกปูเรียงมาแล้วตามข้อกำหนดของ NOAA ดังนั้นผมจะอ่านไฟล์แอสกี้นี้แล้วแปลงเป็นไบนารี เพียงแต่เพิ่มส่วนหัวเข้าไป

ตัวอย่างโปรแกรมไพทอนสำหรับแปลงไฟล์แบบจำลองความสูงจีออยด์

ผมเขียนสคริปต์ภาษาไพทอน เผื่อมีใครสนใจ ผมตึ้งชื่อไฟล์ว่า “rtsd2gtx.py” โปรแกรมมีขนาดเล็กมากๆ ไม่กี่บรรทัด หลักการคืออ่านไฟล์แอสกี้ “tgm2017.asc” มาแล้วเขียนไฟล์ไบนารีในรูปแบบ gtx ข้อควรระวังคือไฟล์ไบนารี ต้องเป็นแบบ Big Endian ในโมดูล struct ผมจึงใส่เครื่องหมาย “>” ไปด้วย

ตัวอย่างเขียนขนิดข้อมูลเป็น double ใช้คำสั่ง f.write(struct.pack(“>d”, 3.0))
เขียนข้อมูลเป็น integer ใช้คำสั่ง f.write(struct.pack(“>i”, 1200))

<import struct
def readRTSDAsciiFile():
    geoid = []
    with open('tgm2017.asc', 'r') as f:
        geoid = [[float(n) for n in line.split()] for line in f] #2D list, one list contain one row.
    #TGM2017 was organized already from lowest row (min latitude) to highest row (max latitude).
    #No need to reverse.	
    return geoid

def writeGTXBinaryFile(geoid):
    with open('tgm2017.gtx', 'wb') as f:
        # pack with ">" for big endian.
		# write Header of file
        f.write(struct.pack(">d", 3.0))  #lower left Latitude
        f.write(struct.pack(">d", 95.0)) #lower left Longtitude		
        f.write(struct.pack(">d", 1.0/60.0)) #delta Latitude = 1 second
        f.write(struct.pack(">d", 1.0/60.0)) #delta Longitude = 1 second	
        f.write(struct.pack(">i", 1200)) #number of rows
        f.write(struct.pack(">i", 780))	#number of columns
		# write height values.
        for row in geoid:
            for c in row:
                f.write(struct.pack(">f", c))

# main program	
g = readRTSDAsciiFile()
writeGTXBinaryFile(g)

ณ ตอนนี้ผมสร้างโฟลเดอร์ เอาไฟล์ tgm2017.asc มาวางไว้ และมีโปรแกรมสคริปต์ “rtsd2gtx.py” อยู่ในโฟลเดอร์เดียวกัน

โฟลเดอร์ที่เก็บไฟล์

จากนั้นทำการรันโปรแกรมด้วยคำสั่งที่คอมมานด์ไลน์ python rtsd2gtx.py รอแป๊ปหนึ่งไฟล์ gtx ที่เก็บแบบจำลองความสูงจีออยด์จะถูกสร้าง และมีขนาดเล็กลงจากไฟล์แอสกี้ดั้งเดิมประมาณ 3 เท่า

ไฟล์ tgm2017.gtx ขนาดประมาณ 3.57 MB

ตัวอย่างโปรแกรมไพทอนสำหรับคำนวณหาความสูงจีออยด์

ต่อไปโปรแกรมไพทอนที่จะนำมาทดสอบคำนวณหาความสูงจีออยด์ ผมตั้งชื่อง่ายๆว่า “test.py” ถ้าจะลองรันโปรแกรมนี้เพื่อทดสอบต้องติดตั้งไลบรารี pyproj ให้เรียบร้อยก่อน

โค้ดโปรแกรมเริ่มต้นจากสร้าง projection ต้นทาง (ตัวแปร wgs84 egm) โดยที่กำหนด +geoidgrids=tgm2017.gtx และสร้าง projection ปลายทาง (ตัวแปร wgs84) โดยที่เว้นไม่ใส่แบบจำลองความสูงจีออยด์

import pyproj # Import the pyproj module
# Define source coordinate system with TGM2017.
wgs84egm = pyproj.Proj("+init=EPSG:4326 +geoidgrids=tgm2017.gtx") 
# Define target coordinate system without geoid model.
wgs84 = pyproj.Proj("+init=EPSG:4326")

lat, lon = 18.3353398540, 99.371215293000
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("1) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 7.75906518100, 98.303594340000
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("2) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 9.18558870000, 99.843706600000
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("3) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 6.96243169, 99.77398865
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("4) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 7.20852844, 99.72353542
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("5) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 7.35631365, 100.12797020
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("6) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 7.23806984, 100.55255020
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("7) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 7.09316728, 100.56368280
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("8) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 6.99643503, 100.43034140
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("9) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

lat, lon = 13.0+42.0/60+38.64/3600, 100+29.0/60+13.02/3600
x, y, z = pyproj.transform(wgs84egm, wgs84, lon, lat, 0.0)
print("10) Latitude = %.8f, Longitude = %.8f, Geoid Height = %.5f" % (y, x, z))

เมื่อจัดเก็บไฟล์เรียบร้อย ในตอนนี้ ถ้าดูที่โฟล์เดอร์จะเห็นไฟล์ดังนี้

จุดทดสอบ

ถ้าดูในโค้ดจะเห็นจุดทดสอบทั้งหมด 10 จุด โดยที่ 9 จุดแรกผมนำมาจากไฟล์ INPUT.DAT ที่ติดมากับโปรแกรม TGM2017.EXE ถ้าเปิดดูด้วย Notepad จะเห็นดังนี้

จุดทดสอบ

ส่วนจุดที่ 10 ได้มาจากสไลด์ ชื่อ BM11SP Latitude = 13° 42′ 38.64″ N Longitude = 100° 29′ 13.02″E

คำนวณ

จากนั้นทำการรันโปรแกรมบนคอมมานด์ไลน์ ใช้้คำสั่ง python test.py จะได้ผลลัพธ์ดังนี้

คำนวณหาความสูงจีออยด์

ผมนำผลลัพธ์มาเปรียบเทียบกับที่คำนวณด้วย TGM2017.EXE พบว่ามีความต่างกันเล็กน้อยที่หลักมิลลิเมตร สาเหตุน่าจะเกิดตอน interpolation โดยการนำจุดรอบๆจากแบบจำลองความสูงจีออยด์ ซึ่งโปรแกรมอาจจะเลือกใช้แบบ BiCubic หรือ BiLinear ผมไม่แน่ใจว่า Proj.4 ใช้แบบไหน ตอนนี้ยังไม่มีโอกาสไปดูโค้ดต้นฉบับ

เปรียบเทียบผลลัพธ์การคำนวณ

การพัฒนาโปรแกรมสำหรับการใช้งานในอนาคต

ผมวางแผนว่าจะนำแบบจำลองความสูงจีออยด์ TGM2017 ไปคำนวณในโปรแกรมรวมเครื่องมือฉบับกระเปาสำหรับช่างสำรวจ Surveyor Pocket Tools ได้แก่การคำนวณความสูงจีออยด์ การคำนวณหาสเกลแฟคเตอร์

สรุป

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

แก้ไขจอดับ: Surveyor Pocket Tools แสดงแผนที่บน Google Maps (สัจธรรมโลกนี้ไม่มีอะไรฟรี)

ตั้งแต่วันที่ 16 มิถุนายน 2018 ที่ผ่านมา ถ้าผู้ใช้ใช้งาน Surveyor Pocket Tools ลองปักหมุดผ่านโปรแกรมนี้ จะเห็นว่าจอดับแสดงข้อความว่า “For development purpose only” เพราะว่ากูเกิ้ลเปลี่ยนมาเก็บเงินผู้ใช้โดยเฉพาะผู้พัฒนาโปรแกรม โดยที่ผู้พัฒนาจะต้องขอ API Key จากทางกูเกิ้ลก่อน แล้วมาลงนามในบัญชีพร้อมจะเป็นหนี้กูเกิ้ล ถ้าใช้เกินกำหนด ผมมานั่งพิจารณาหลายเดือนว่าจะเอาไงดี โปรแกรมแจกให้ใช้ฟรีไม่มีเงื่อนไข แต่การที่ต้องไปจ่ายเงินให้กูเกิ้ลเพื่อการนี้ก็ดูกระไรๆ (ถึงแม้จะมีแบบ premium ให้องค์กรไม่หวังกำไรใช้ฟรี แต่ก็ยุ่งยากเกินไปสำหรับผม)

จอดับกรณีที่กูเกิ้ลเปลี่ยนนโยบาย Google Maps API

เอาละครับเมื่อคิดได้ปลงตก ก็มาดูว่ากูเกิ้ลเขาให้ใช้ฟรีได้เท่าไหร่เมื่อใช้แผนที่แบบ static สำหรับโปรแกรมบน PC Desktop ตอนแรกกูเกิ้ลให้ใช้ฟรีประมาณ 2500 ครั้งต่อวัน (นับตัวเลขที่มีการ request ไปที่ server ของกูเกิ้ล) ไม่นาน Wikipedia กับ Foursquare สองเจ้านี้ย้ายไปใช้แผนที่ตัวอื่น ทำให้กูเกิ้ลต้องลดราคาลง และเพิ่มจำนวนให้ใช้ฟรีเป็น 25000 ครั้งต่อวัน ผมดูแล้วโปรแกรม Surveyor Pocket Tools ที่ผู้ใช้วันๆหนึ่งแล้วปักหมุดคงไม่มากเท่านี้ ก็เลยมาลองดูสักตั้ง

เปลี่ยนวิธีการแสดงผล

เดิมผมใช้วิธีการสร้างไฟล์ html ที่เครื่องโลคอลคือสร้างไว้ที่เครื่องคอมพิวเตอร์ของผู้ใช้โปรแกรม เวลาผู้ใช้ปักหมุดแค่เรียกใช้ web browser เช่น chrome หรือ firefox ที่ติดตั้งอยู่ก่อนแล้ว เมื่อเปิดใช้ไฟล์ html ก็จะสามารถออนไลน์ดูแผนที่ที่ปักหมุดได้บน Google Maps แต่เมื่อมาจอดับ ผมไม่สามารถใช้วิธีการนี้ได้อีกต่อไป เพราะในไฟล์ html ผมต้องใส่คีย์ (API Key) ของผมลงไปด้วย เผื่อมีคนเปิดไฟล์นี้เอาคีย์ของผมไปใช้เกินอัตราก็บรรลัยจ่ายเงินกันอาน

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

แสดงผล Google Maps จากเอนจินเว็บของ PySide2
ชยายละเอียดในโหมด Map
ดูภาพถ่ายดาวเทียมในโหมด Satellite

ผมปรับโปรแกรมเป็นรุ่น 1.01 ผมอัพโหลดโปรแกรม Survey Pocket Tools รุ่น 1.01 ตัวใหม่ให้ดาวน์โหลดไปใช้ ไปที่หน้าดาวน์โหลดได้ตามลิ๊งค์นี้ (มีเฉพาะรุ่น 64 บิต ส่วน 32 บิตมีปัญหา build ไม่ผ่าน) ติดตามกันต่อไปครับ

Surveyor Pocket Tools version 1.01
ทดสอบเขียนโปรแกรมไพทอน (Python)  บนเครื่องคิดเลข Casio fx-cg50 Prizm

ทดสอบเขียนโปรแกรมไพทอน (Python) บนเครื่องคิดเลข Casio fx-cg50 Prizm

ไพทอนบนเครื่องคิดเลข

ช่วงนี้ผมมีโอกาสทำงานใกล้ชิดกับภาคสนาม ทำให้มีโอกาสได้จับและใช้เครื่องคิดเลขมากกว่าปกติ ในเวลาที่ผ่านมาไม่ถึงเดือนผมได้ซื้อเครื่องคิดเลข Casio fx-CG50 Prizm เคสสีขาว ที่ซื้อมาเพราะทราบว่าถ้า update OS เป็นรุ่น 3.20 จะสามารถใช้ ไพทอน (Python) ได้ ก็ขอหมายเหตุสักนิดว่าเป็นไมโครไพทอน (Micropython) ที่ทางทีมงาน Micropython ได้พอร์ตออกมาให้มีขนาดเล็กเพื่อเอาไปรันในบอร์ด iOT ได้ หรือบอร์ดที่เป็นไมโครคอนโทรลเลอร์ทั้งหลาย เน้นขนาดเล็ก หน่วยความจำต่ำ กินไฟน้อย ต่ออินเทอร์เน็ตได้ในตัว ผมจะไม่มุ่งไปทางนี้หรอกครับ ในบทความนี้ แต่จะพูดถึงเครื่องคิดเลขคาสิโอ ที่นำเอาไมโครไพทอนมาลงเครื่องคิดเลขรุ่นนี้ เพราะว่าไมโครไพทอนกินหน่วยความจำต่ำ ก็เลยเหมาะสมที่จะเอามารันในเครื่องคิดเลขที่มีทรัพยากรต่ำอยู่แล้ว ให้เกิดประสิทธิภาพมากยิ่งขึ้นไป

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

สำหรับเครื่องคิดเลขที่ใช้ในแวดวงวิศวกรรม โปรแกรมที่เขียนด้วยไมโครไพทอนที่มีไลบรารี math หรืออาจจะเสริมด้วยไลบรารีพวกเมตริก (matrix) น่าจะพอนำมาเขียนใช้งานกันได้

 การพัฒนาโปรแกรมด้วยชุดพัฒนาโปรแกรมภาษาซี (Software Development Kit)

นี่เป็นความข้องใจของผมในฐานะแฟนเครื่องคิดเลขคาสิโอ รุ่น fx-9860G ทางคาสิโอจัดทำ SDK ให้สามารถเขียนโปรแกรมด้วยภาษาซี ที่ผมเขียนโปรแกรมมาแจกใช้ในแวดวงงานสำรวจแล้วหลายโปรแกรม แต่รุ่นนี้กลับไม่ทำมาให้  (ที่จริงไม่ทำมาให้ตั้งแต่ fx-CG10/fx-CG20)ไม่ทราบว่าเพราะเหตุใด เครื่องคิดเลขรุ่นนี้ไม่มี SD Card แต่ทดแทนด้วยการใส่ Flash memory มา 16 MB  ซึ่งก็พอจะใส่โปรแกรมใช้งานได้มากโขอยู่ หรือคิดว่ามีไมโครไพทอน มาให้แล้วน่าจะตอบโจทย์ได้หมด แต่ผมก็ไม่คิดอย่างนั้น ยังมีโปรแกรมเมอร์ภาษาซีอีกพอสมควร และในสภาพแวดล้อมของเครื่องคิดเลขจริงๆ โปรแกรมที่เขียนด้วยภาษาซีจะเร็วกว่าไพทอนอยู่แล้ว แต่ไพทอนได้เปรียบในด้านความง่าย

 เครื่องมือพัฒนาโปรแกรมของชุมชน

ยังมีชุมชนของนักพัฒนาที่สร้าง SDK  ขึ้นมาใช้งานเอง มีประมาณ 2-3 กลุ่มแต่สุดท้ายดูเหมือนไม่มีความเคลื่อนไหวกันมาหลายปีแล้ว เครื่องมือที่มีชื่อเสียงมากที่สุดคือ PrizmSDK และอีกอันคือ mini-SDK ผมเองใช้ไลบรารี MyLib แต่เผอิญผู้พัฒนาได้ทำไว้สำหรับเครื่อง fx-9860G เท่านั้น ไม่เป็นไรขอมุ่งลองไพทอนบนเครื่องคิดเลขรุ่น fx-CG50 นี้ก่อน ถ้าพัฒนาโปรแกรมด้วยภาษาซี ผมก็ยังมุ่งไปที่เครื่องคิดเลข fx-9860G เหมือนเดิม 

ผมลองเขียนโปรแกรมทดสอบเล็กๆลองดูด้วยเครื่องมือ PrizmSDK ก็ได้ดังรูปข้างล่าง (โปรแกรมไม่มีอะไรมีแต่เมนู) เทียบกับโปรแกรม System Manager ที่มากับเครื่อง

คุณสมบัติของเครื่องคิดเลข

โดยรวมรวมแล้วเครื่องคิดเลขนั้นเหมาะสำหรับนักศึกษามาก เพราะมีฟังก์ชั่นคณิตศาสตร์ การเงิน สถิติ มีกราฟมากมายให้ใช้ แต่สำหรับผมแล้วไม่มีอะไรต้องใช้เลย ยกเว้นเรื่องโปรแกรมบนเครื่องคิดเลขอย่างเดียว ถ้าไม่มีสิ่งนี้ก็ใช้เป็นที่ทับกระดาษได้เลย เครื่องรุ่นนี้ใช้โปรเซสเซอร์ตระกูล SH4 ขนาดหน้าจอ 384 x 216 จอ LCD จำนวนสี 65000 สี มีความสว่างพอสมควรและปรับได้ ความกว้างหน้าจอแบบทะแยง 3.17 นิ้ว หน่วยความจำของเครื่อง 60 KB มี Flash memory ที่สามารถเขียนอ่านได้ 16  MB ซึ่งจะเป็นที่เอาไว้เก็บโปรแกรมหรือข้อมูล ใช้ถ่าน AAA 4 ก้อน  เท่าที่ผมเปิดเครื่องใช้บ้างในแต่ละวันมาประมาณสองสัปดาห์ พบว่าแบตเตอรี่ลดลงมานิดหนึ่ง อนาคตอาจจะหาถ่านชาร์จมาใช้ ตอนนี้ใส่อัลคาไลน์ไปก่อน

ประเดิมโปรแกรมด้วยไพทอน

จะลองโปรแกรมทั้งทีผมพยายามให้โปรแกรมมีขนาดซับซ้อนมานิดหนึ่ง  และเรียกใช้โมดูลด้วย คิดไปคิดมาก็เลยจะลองโปรแกรมแปลงค่าพิกัดระหว่างค่าพิกัดภูมิศาสตร์กับค่าพิกัดยูทีเอ็ม เนื่องจากไม่ค่อยมีเวลาเขียน เลยลองหาไลบรารีที่ท่านอื่นได้ทำไว้ ผมเคยเกริ่นไปแล้วข้างต้นว่าไลบรารีรุ่นใหญ่เช่น pyproj ไม่สามารถเอามาใช้ได้ ลองค้นดูพบว่ามีไลบรารีไพทอนเล็กๆ ชื่อ utm มีสัญญาอนุญาตเป็น MIT-License ผมเอาโค้ดมาดัดแปลงนิดหน่อยให้เหมาะสมกับเครื่องคิดเลข แล้วเขียนไปอยู่ในไฟล์ utm.py เพื่อให้สะดวกเวลาเรียกใช้

การเขียนโปรแกรมไพทอน ถ้าไปเขียนบนเครื่องคิดเลข จะชักช้าเสียเวลาครับ เนื่องจากไมโครไพทอน พอร์ตไลบรารีเช่น math แล้ว ดังนั้นถ้าโปรแกรมของเราไม่ได้ใช้อะไรพิศดารมาก ก็สามารถมาเขียนโค้ดบน PyCharm หรือ Idle ได้ ผมเลือก PyCharm เมื่อทดสอบโปรแกรมเสร็จสามารถ โอนโปรแกรมเข้าไปไว้ในเครื่องด้วยการต่อเครื่องคิดเลขด้วยสาย USB เข้ากับคอมพิวเตอร์ที่รันวินโดส์ตามผังด้านล่าง

เมื่อรันโปรแกรมได้ตามความต้องการแล้วจากนั้นก็เอาเครื่องคิดเลขมาต่อกับคอมพิวเตอร์ วินโดส์จะมองเห็นเป็นไดรว์ สามารถใช้ File Explorer ก๊อปปี้โปรแกรมจากคอมพิวเตอร์ไปยังเครื่องคิดเลขได้ ตัวโค้ดโปรแกรมไลบรารีดูได้ด้านล่างครับ

[code language=”python” collapse=”true”]

import math
K0 = 0.9996

E = 0.00669438
E2 = E * E
E3 = E2 * E
E_P2 = E / (1.0 – E)

SQRT_E = math.sqrt(1 – E)
_E = (1 – SQRT_E) / (1 + SQRT_E)
_E2 = _E * _E
_E3 = _E2 * _E
_E4 = _E3 * _E
_E5 = _E4 * _E

M1 = (1 – E / 4 – 3 * E2 / 64 – 5 * E3 / 256)
M2 = (3 * E / 8 + 3 * E2 / 32 + 45 * E3 / 1024)
M3 = (15 * E2 / 256 + 45 * E3 / 1024)
M4 = (35 * E3 / 3072)

P2 = (3. / 2 * _E – 27. / 32 * _E3 + 269. / 512 * _E5)
P3 = (21. / 16 * _E2 – 55. / 32 * _E4)
P4 = (151. / 96 * _E3 – 417. / 128 * _E5)
P5 = (1097. / 512 * _E4)

R = 6378137

ZONE_LETTERS = "CDEFGHJKLMNPQRSTUVWXX"

class OutOfRangeError(ValueError):
pass

def to_latlon(easting, northing, zone_number, hemi):

northern = (hemi == ‘N’)

x = easting – 500000
y = northing

if not northern:
y -= 10000000

m = y / K0
mu = m / (R * M1)

p_rad = (mu +
P2 * math.sin(2 * mu) +
P3 * math.sin(4 * mu) +
P4 * math.sin(6 * mu) +
P5 * math.sin(8 * mu))

p_sin = math.sin(p_rad)
p_sin2 = p_sin * p_sin

p_cos = math.cos(p_rad)

p_tan = p_sin / p_cos
p_tan2 = p_tan * p_tan
p_tan4 = p_tan2 * p_tan2

ep_sin = 1 – E * p_sin2
ep_sin_sqrt = math.sqrt(1 – E * p_sin2)

n = R / ep_sin_sqrt
r = (1 – E) / ep_sin

c = _E * p_cos**2
c2 = c * c

d = x / (n * K0)
d2 = d * d
d3 = d2 * d
d4 = d3 * d
d5 = d4 * d
d6 = d5 * d

latitude = (p_rad – (p_tan / r) *
(d2 / 2 –
d4 / 24 * (5 + 3 * p_tan2 + 10 * c – 4 * c2 – 9 * E_P2)) +
d6 / 720 * (61 + 90 * p_tan2 + 298 * c + 45 * p_tan4 – 252 * E_P2 – 3 * c2))

longitude = (d –
d3 / 6 * (1 + 2 * p_tan2 + c) +
d5 / 120 * (5 – 2 * c + 28 * p_tan2 – 3 * c2 + 8 * E_P2 + 24 * p_tan4)) / p_cos

return (180/math.pi*(latitude),
180/math.pi*(longitude) + zone_number_to_central_longitude(zone_number))

def from_latlon(latitude, longitude, force_zone_number=None):
if not -80.0 &amp;amp;amp;lt;= latitude &amp;amp;amp;lt;= 84.0:
raise OutOfRangeError(‘latitude out of range (must be between 80 deg S and 84 deg N)’)
if not -180.0 &amp;amp;amp;lt;= longitude &amp;amp;amp;lt;= 180.0: raise OutOfRangeError(‘longitude out of range (must be between 180 deg W and 180 deg E)’) lat_rad = math.pi/180*(latitude) lat_sin = math.sin(lat_rad) lat_cos = math.cos(lat_rad) lat_tan = lat_sin / lat_cos lat_tan2 = lat_tan * lat_tan lat_tan4 = lat_tan2 * lat_tan2 if force_zone_number is None: zone_number = latlon_to_zone_number(latitude, longitude) else: zone_number = force_zone_number #zone_letter = latitude_to_zone_letter(latitude) if (latitude &amp;amp;amp;gt;= 0):
hemi = ‘N’
else:
hemi = ‘S’

lon_rad = math.pi/180*(longitude)
central_lon = zone_number_to_central_longitude(zone_number)
central_lon_rad = math.pi/180*(central_lon)

n = R / math.sqrt(1 – E * lat_sin**2)
c = E_P2 * lat_cos**2

a = lat_cos * (lon_rad – central_lon_rad)
a2 = a * a
a3 = a2 * a
a4 = a3 * a
a5 = a4 * a
a6 = a5 * a

m = R * (M1 * lat_rad –
M2 * math.sin(2 * lat_rad) +
M3 * math.sin(4 * lat_rad) –
M4 * math.sin(6 * lat_rad))

easting = K0 * n * (a +
a3 / 6 * (1 – lat_tan2 + c) +
a5 / 120 * (5 – 18 * lat_tan2 + lat_tan4 + 72 * c – 58 * E_P2)) + 500000

northing = K0 * (m + n * lat_tan * (a2 / 2 +
a4 / 24 * (5 – lat_tan2 + 9 * c + 4 * c**2) +
a6 / 720 * (61 – 58 * lat_tan2 + lat_tan4 + 600 * c – 330 * E_P2)))

if latitude &amp;amp;amp;lt; 0:
northing += 10000000

return easting, northing, zone_number, hemi

def latitude_to_zone_letter(latitude):
if -80 &amp;amp;amp;lt;= latitude &amp;amp;amp;lt;= 84: return ZONE_LETTERS[int(latitude + 80) &amp;amp;amp;gt;&amp;amp;amp;gt; 3]
else:
return None

def latlon_to_zone_number(latitude, longitude):
if 56 &amp;amp;amp;lt;= latitude &amp;amp;amp;lt; 64 and 3 &amp;amp;amp;lt;= longitude &amp;amp;amp;lt; 12:
return 32

if 72 &amp;amp;amp;lt;= latitude &amp;amp;amp;lt;= 84 and longitude &amp;amp;amp;gt;= 0:
if longitude &amp;amp;amp;lt;= 9:
return 31
elif longitude &amp;amp;amp;lt;= 21:
return 33
elif longitude &amp;amp;amp;lt;= 33:
return 35
elif longitude &amp;amp;amp;lt;= 42:
return 37

return int((longitude + 180) / 6) + 1

def zone_number_to_central_longitude(zone_number):
return (zone_number – 1) * 6 – 180 + 3

[/code]

โปรแกรมแปลงพิกัดภูมิศาสตร์ในภาคไพทอน

ผมเขียนไพทอนเป็นโมดูลอีกโมดูลเพื่อเรียกใช้ไลบรารี ตั้งชื่อว่า UTM2GEO.py โดยที่เขียนเมนูติดต่อการใช้งานง่ายๆ

[code language=”python” collapse=”true”]
from utm import *

def print_menu():
print(5*’-‘,"MENU",5*’-‘)
print(‘1: UTM to GEO’)
print(‘2: GEO to UTM’)
print(‘0: Exit’)

def geo2utm(lon,lat):
east,north,zn,hem=from_latlon(lat,lon)
print("North={0:11.3f}".format(north))
print("East={0:10.3f}".format(east))
print("UTM Zone No={0:0d}{1}".format(zn,hem))

def utm2geo(e,n,zoneno,hemi):
lat,lon=to_latlon(e,n,zoneno,hemi)
print("Latitude={0:11.7f}".format(lat))
print("Longitude={0:10.7f}".format(lon))

loop=True
while loop:
print_menu()
choice=int(input(‘Selection[0-2]’))
if (choice==0):
loop=False
elif (choice==1):
loop=True
y=float(input("Northing="))
x=float(input("Easting="))
zn=int(input("Zone No="))
hem=input("Hemi (N/S)=")
utm2geo(x,y,zn,hem)
elif (choice==2):
loop=True
y=float(input("Latitude="))
x=float(input("Longitude="))
geo2utm(x,y)

[/code]

วิธีก๊อปปี้โปรแกรม

จากนั้นผมก๊อปปี้สองไฟล์คือ utm.py  และ UTM2GEO.py ลงบนไดรว์เครื่องคิดเลขดังนี้

ผมเก็บไว้ที่ไดเรคทอรี \SAVE-F\PROGRAM เวลาจะถอดสาย USB เพื่อเลิกการเชื่อมต่อต้อง Safely removal โดยการคลิกเมาส์ขวา “Eject” ที่ File Explorer จากนั้นมาที่เครื่องคิดเลขจากให้กดคีย์ “EXE” และ “EXIT” ตามลำดับ ถ้าไม่ทำไฟล์อาจจะไม่ได้ซิงค์กันอาจจะหายหรือไม่สมบูรณ์ได้ ที่เครื่องคิดเลขกดคีย์ “MENU” เลือก “Python

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

จากรูปด้านบนกดคีย์ F1-Run จะเห็นหน้าจอขึ้นเมนูติดต่อมาง่ายๆ

เราจะเลือกแปลงพิกัดจากค่าพิกัดภูมิศาสตร์ไปเป็นค่าพิกัดยูทีเอ็มเลือกกดคีย์ “2” ที่เครื่องคิดเลขแล้วกดคีย์ “EXE” ป้อนค่าพิกัด Latitude = 39.95259668 Longitude  = -75.15132081 (ป้อนเป็นหน่วยดีกรี ในตอนนี้ยังไม่รับค่าแบบอื่น) จะได้ผลการคำนวณออกมา เนื่องจากในตอนนี้ไม่มีคำสั่งเบรคการแสดงผลเมื่อเขียนด้วยไพทอน (เอาละจะมาบ่นทีหลัง ว่าใส่ไพทอนมาแล้วทางคาสิโอไม่ให้เครื่องมืออะไรมาเลย) การจะดูผลลัพธ์ ผู้ใช้ต้องกดคีย์ “0” เพื่อออกจากโปรแกรมและใช้ลูกศรกดขึ้นไปทางด้านบนเพื่อไปดูผลลัพธ์

จะได้ค่า Northing = 4422506.896 Easting = 487074.371 อยู่ในโซน 18N

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

ทำการรันโปรแกรมใหม่อีกครั้ง ที่เมนูเลือกกดเลข “1” ป้อนค่าพิกัด Northing = 2642783.110 Easting =232030.949 UTM Zone No = 46 Hemi = N

ดูค่าผลลัพธ์ได้ (กดคีย์ “0” ออกจากเมนูก่อนแล้วเลื่อนขึ้นไปดู)

สรุปการใช้งาน

ตอนแรกผมคาดหวังจากที่ทางคาสิโอเอาไมโครไพทอนมาลงเครื่องคิดเลขรุ่นนี้ ยังไงการเขียนโปรแกรมใช้งานยังไงๆผลลัพธ์ที่ออกมาก็ต้องระดับเทพ เพราะไพทอนมันทรงพลังด้วยตัวของมัน แต่เมื่อลองแล้วผิดหวังมาก จนบัดนี้คาสิโอ้ยังไม่ได้ออกคู่มือแสดงฟังก์ชั่นที่ไพทอนสามารถเรียกมาใช้ได้ มีฟังก์ชั่น input กับ print สองฟังก์ชั่นนี้เท่านั้น เพียงแค่ผมค้นหาฟังก์ชั่น clear screen หน้าจอยังทำไม่ได้ ฟังก์ชั่นที่ต้องการสนับสนุนได้แก่การเขียนเมนูที่เรียกใช้ด้วยคีย์ F1 ถึง F6 การปริ๊นท์แสดงสีต่างๆ การเรียกใช้ฟังก์ชั่นกราฟต่างๆหรือพล็อทกราฟ หรือใช้งานเมตริก เป็นต้น

เอาละตอนนี้ไพทอนที่ปรากฎบน OS รุ่น 3.20 เพิ่งออกมาเตือนตุลาคม 2018 (ขณะที่เขียนบทความนี้เดือนพฤศจิกายน 2018) คงต้องให้เวลาสักพักว่าจะเป็นอย่างไร บอกตามตรงว่าคงต้องเอาเครื่องคิดเลขรุ่นนี้มาทับกระดาษอีกสักพักใหญ่ๆ

คอมไพล์ 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 ต่อไป พบกันตอนหน้าครับ

ก้าวไปอีกหนึ่งก้าวกับ XSection Plot

สวมวิญญานใหม่ด้วย PySide2

หลังจากผมคอมไพล์ XSection Plot ใหม่ด้วยสภาวะแวดล้อมพัฒนาของ Qt5 platform ด้วย PySide2 ผมเปลี่ยนลิขสิทธิ์ของโปรแกรมเดิมที่กำกวมออกมาฟรีสมบูรณ์แบบเหมือนกันกับ Surveyor Pocket Tools สามารถนำไปทำซ้ำแจกจ่ายได้ตามอัธยาศัย แต่ห้ามดัดแปลง ห้ามนำไปจำหน่ายหรือให้เช่า

XSection Plot
Copyright (C) Prajuab Riabroy. All Rights Reserved.

XSection Plot is free for use in any environment, including but not necessarily limited to: personal, academic, commercial, government, business, non-profit, and for-profit. "Free" in the preceding sentence means that there is no cost or charge associated with the installation and use of XSection Plot. 
Permission is hereby granted, free of charge, to any person obtaining a copy of this software (the "Software"), to use the Software without restriction, including the rights to use, copy, publish, and distribute the Software, and to permit persons to whom the Software is furnished to do so.

You may not modify, adapt, rent, lease, loan, sell, or create derivative works based upon the Software or any part thereof. 

The above copyright notice and this permission notice shall be included in all copies of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

แก้ไข bugs

นอกจากย้ายโค้ดมาใช้ PySide2 แล้ว ผมเลยถือโอกาสแก้บั๊กเล็กน้อยไปหลายอย่าง เช่น

  • เวลาคลิกขวาเพื่อเรียกเมนูในช่องป้อนข้อมูล โปรแกรมจะ terminate ทันที
  • ใน Section Layout ตรง Horizontal Grid เมื่อปรับ Distance from CL to left ไปแล้ว โปรแกรมไม่จำค่าใหม่
  • อื่นๆอีกเล็กน้อยประมาณสิบกว่าอย่าง

คอมไพล์และสร้างไฟล์ execute binary ด้วย PyInstaller

ตอนนี้โปรแกรมสนับสนุนทั้ง 32 บิตและ 64 บิต ผมใช้ PyCharm เป็นทูลส์ในการพัฒนา และเลือกได้ว่าจะใช้ 32 บิตหรือ 64 บิต เมื่อโปรแกรม stable แล้ว ก็จะสร้าง execute binary file ด้วย PyInstaller ตามสภาวะแวดล้อม ต้องทำสองครั้ง ครั้งแรก 32 บิตและครั้งที่สอง 64 บิต โดยแต่ละครั้งจะได้ไฟล์ exe, pyd, dll รวมถึงไลบรารีของไพทอนที่เราเรียกใช้ และที่สำคัญคือไลบรารีของ PySide2

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

ไฟล์ที่ได้จาก PyInstaller ทั้งหมด ผมจะนำมาสร้างไฟล์ Setup ด้วย Inno Setup เพื่อนเก่าที่ใช้กันมานมนาน มีดีเพียงพอที่จะสร้างไฟล์ Setup ได้ง่ายๆ มี options ให้เลือกพอสมควร สุดท้ายจะได้ไฟล์ Setup ที่เป็น Execute file ไฟล์เดียวพร้อมจะนำไปอัพโหลดให้ผู้ใช้นำไปใช้งานได้

ทดสอบโปรแกรมด้วยแบบรูปตัดตามยาว

ผมจะลองทดสอบโปรแกรมจากข้อมูล ความจริง XSection Plot คือโปรแกรมสร้างหรือช่วยเขียนรูปตัดตามขวาง แต่ยังพอเอามาประยุกต์ใช้กับ Long Profile ได้ แต่ไฟล์ข้อมูลส่วนใหญ่จะมีข้อมูลเพียงหนึ่ง Section เท่านั้นจะเริ่มจากไฟล์ข้อมูลของ Existing Ground Section ก่อนครับ ข้อมูลดังในกรอบข้างล่าง สามารถ copy ไป paste ในโปรแกรม text file editor เช่น Notepad ได้จากนั้น save ตั้ง extension เป็น gxml (ตัวอย่างผมตั้งชื่อว่า lake-road.gxml)


  
  
  
XSection Plot Prajuab Riabroy 4.1.512 Ground 2017-11-12 19:24:28.634707
Cantonment Lake Road False 500.0 1000.0 10.0 2.0 0 True True False 3 3 True 10.0 MSL MSL 1 2 True True 1 90 10 LT. RT. Km. 2 1 1 0 True False Custom 940.0 200.0 1
47.7 11.7 14.0 29

ต่อไปเป็น Typical Section ขนาดเล็กกว่า ผมตั้งชื่อว่า lake-typical.txml


  
  
  
XSection Plot Prajuab Riabroy 4.1.512 Typical 2017-11-12 18:42:28.929847
1
0.0 0.0 0.0 4

เปิดไฟล์ข้อมูลทดสอบบน XSection Plot

จากนั้นนำสองไฟล์มาเปิดด้วย XSection Plot เวลาเปิดไฟล์ให้เลือกรูปแบบของไฟล์ด้วยจะได้เปิดง่าย  มีชื่อ extension ตามลำดับดังนี้ gxml, txml

จะได้ข้อมูลปรากฎขึ้นบนโปรแกรมดังนี้

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

ผมลองเลือกใช้หน้ากระดาษที่ไม่มาตรฐานเพื่อให้ฟิตกับขนาดรูปตัดตามยาว ยาว 940 มม. และกว้าง 200 มม.

ตั้งค่า (Settings)

ผมตั้งสเกลทางราบเป็น 1:1000 และสเกลทางดิ่ง 1:250 อย่างอื่นดูรูปด้านล่าง

จัดวางรูปตัดบนกระดาษ (Section Layout)

จะเห็นกระดาษขนาด 200 มม. x 940 มม. แล้วเลือกพารามิเตอร์ดังรูปด้านล่าง

ดูรูปตัด (Section Viewer)

จะเห็นรูปตัดตามยาวที่ประกอบไปด้วย Existing Ground และ Typical

ลองซูมดู ก็ได้แบบ drawing มาพอถูๆไถๆ ที่สามารถนำไปเขียนเพิ่มเติมได้ในโปรแกรมด้านเขียนแบบทั้งหลายเช่น Autocad, Microstation, Draftsight

Save to DXF

จัดเก็บไฟล์ในรูป Autocad DXF เพื่อสามารถนำไปเปิดในโปรแกรมอื่นได้

เปิดไฟล์แบบรูปตัดตามยาว

ผมใช้ Microstation เปิดแบบรูปตัดตามยาวได้ผลลัพธ์ดังนี้และพร้อมจะนำแบบไปเขียนเพิ่มเติมตามความต้องการ

ครับคงอีกไม่นานก็จะคงจะปล่อยเวอร์ชั่นเสถียรให้สามารถดาวน์โหลดได้ พบกันใหม่ครับ

แนะนำการย้ายโค้ดจาก PyQt5 เป็น PySide2

ย้ายโค้ด XSection Plot

ในขณะนี้ทำงานอยู่ที่บังคลาเทศ โครงการก่อสร้างรถไฟฟ้าที่กรุงธากา มีโอกาสกลับมาพัก ก็พอมีเวลาว่างพยายามย้ายโค้ดของโปรแกรม XSection Plot จากของเดิมที่พัฒนาด้วย PyQt5 ที่ยังติดเรื่องลิขสิทธิ์บางส่วน โดยย้ายมาใช้ PySide2 ที่เปิดกว้างกว่า ความจริงทั้งคู่ใช้เครื่องยนต์ (Engine) เดียวกันคือ Qt5 platform ดังนั้นเมื่อย้ายโค้ดสำเร็จแล้วเวลารันก็หน้าตาเหมือนกันเป๊ะดังรูปด้านล่างที่คอมไพล์ด้วย PySide2

จัดการปลั๊กอิน PySide2

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

import os
import sys
import PySide2
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', '')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
print('plugin_path = ', plugin_path)

ที่เครื่องคอมพิวเตอร์ผม จะปริ๊นท์พาทของ “plugins” ดังนี้

plugin_path =  C:\Miniconda3\envs\py36_64\lib\site-packages\PySide2\plugins\

เพราะว่าผมใช้ Miniconda เป็นตัวจัดการระบบ environment ของ python ผมสร้าง envs ชื่อ “py36_64” เป็น python รุ่น 3.6 แบบ 64 บิต และ PySide2 ก็จะถูกติดตั้งมาอยู่ภายใต้โฟลเดอร์นี้ อีก envs หนึ่งที่สร้างไว้ชื่อ “py36_32” เป็น python รุ่น 3.6 แบบ 32 บิต เมื่อรันโปรแกรมแล้วจะปริ๊นท์พาทมาดังนี้

plugin_path =  C:\Miniconda3\envs\py36_32\lib\site-packages\PySide2\plugins\

วิธีการสร้าง environment สำหรับไพทอนก็กลับไปดูโพสต์เก่าของผมได้ครับ

เปลี่ยนคำ PyQt5 เป็น PySide2

ยังอยู่ในส่วน import ที่โค๊ดเดิมของโปรแกรมผมเรียกใช้ไลบรารีของ PyQt5 ดังนี้

from PyQt5.QtGui import QPixmap, QIcon, QKeySequence, QFont, QIntValidator, QCursor
from PyQt5.QtCore import Qt, pyqtSlot, QSettings, QFileInfo, QSize, QFile
from PyQt5.QtWidgets import QUndoStack, QSplashScreen, QApplication, QMainWindow, QTabWidget, QAction, QStatusBar,\
     QMenu, QWidget, QSizePolicy, QLineEdit, QFileDialog, QMessageBox, QDesktopWidget

เปลี่ยนเป็น

from PySide2.QtGui import QPixmap, QIcon, QKeySequence, QFont, QIntValidator, QCursor
from PySide2.QtCore import Qt, Slot, QSettings, QFileInfo, QSize, QFile
from PySide2.QtWidgets import QUndoStack, QSplashScreen, QApplication, QMainWindow, QTabWidget, QAction, QStatusBar,\
     QMenu, QWidget, QSizePolicy, QLineEdit, QFileDialog, QMessageBox, QDesktopWidget

ส่วนใหญ่เกือบ 99.99% ที่เหมือนกัน ยกเว้น Signal & Slot

Signal and Slot

มีข้อแตกต่างกันเล็กน้อย เช่นเดิมใน PyQt5 เรียกใช้ pyqtSlot, pyqtSignal ให้เปลี่ยนเป็น Slot, Signal ใน PySide2 ครับ
นอกจากส่วน import แล้ว ในโค๊ดเดิมที่ประกาศคลาส โค้ดเดิมผมเรียกใช้ Signal and Slot ดังนี้

class OverlapSection(QObject):
    '''Horizontal & Vertical overlapped.'''
    overlapped = pyqtSignal(str)

    def __init__(self):
            QObject.__init__(self)

    def emitOverlapSignal(self, message):
        self.overlapped.emit(message)

เปลี่ยนใหม่เป็น

class OverlapSection(QObject):
    '''Horizontal & Vertical overlapped.'''
    overlapped = Signal(str)

    def __init__(self):
            QObject.__init__(self)

    def emitOverlapSignal(self, message):
        self.overlapped.emit(message)

ติดตั้ง PySide2 จากไฟล์ wheel

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

PySide2-5.6-cp35-cp35m-win32.whl 	 
PySide2-5.6-cp35-cp35m-win_amd64.whl 	 
PySide2-5.6-cp36-cp36m-win32.whl 
PySide2-5.6-cp36-cp36m-win_amd64.whl

จะเห็นว่า PySide2 สนับสนุนทั้งไพทอน 3.5 และ 3.6 และในตอนนี้ Qt5 รุ่น  5.6 สำหรับคำสั่งที่ติดตั้งก็ง่ายๆใช้ pip ตามด้วยชื่อไฟล์ wheel

pip install PySide2-5.6-cp36-cp36m-win_amd64.whl

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

การตั้งค่า (Settings) ของ Surveyor Pocket Tools

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

python_2017-07-15_09-19-59

เมื่อดับเบิ้ลคลิกเข้าไปจะเห็นไดอะล็อก

python_2017-07-15_09-24-21

จะมีแท็บ Unit, Linear Precision, Angular Precision, Google Maps และ Google Earth เรียงรายกันตามลำดับ เริ่มต้นที่ Unit ออกแบบเพื่ออนาคตสำหรับหน่วยอื่นที่ไม่ใช่หน่วย metric แต่ตอนนี้สนับสนุนหน่วยเมตริกอย่างเดียวครับ

Linear Precision

มาดูที่ Linear Precision คือตั้งความละเอียดหรือจำนวนทศนิยมให้กับหน่วยที่เป็นเชิงเส้นทั้งหลายเช่นระยะทาง ความสูง พื้นที่หรือแม้กระทั่งจำนวนทศนิยมค่าพิกัดของระบบพิกัดฉาก และจำนวนทศนิยมของสเกลแฟคเตอร์

python_2017-07-15_10-47-21

ลองคำนวณการแปลงพิกัดด้วยทูลส์ Transform Coordinates ตรวจสอบจำนวนทศนิยม

python_2017-07-15_10-45-24

Angular Precision

สำหรับ Angular Precision ตั้งความละเอียดหรือจำนวนทศนิยมของมุมทั้งหลายเช่นค่าพิกัดในระบบภูมิศาสตร์หรือมุม convergence ดังรูปด้านล่าง

python_2017-07-15_11-19-19

การใช้งานลองดู UTM-Geo Converter 

python_2017-07-15_11-22-39

python_2017-07-15_11-25-01

ตั้งค่าสำหรับ Google Maps

ตั้งค่าสำหรับปักหมุดบน Google Maps ได้แก่รูปแบบของหมุด สี ดังรูปด้านล่าง

python_2017-07-15_11-27-06

ตัวอย่างการใช้งานโดยใช้ทูลส์ Geodesic Distance 

python_2017-07-15_11-36-42

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

firefox_2017-07-15_11-36-59

ตัวอย่างการใช้งานคำนวณหาพื้นที่ Compute Area 

python_2017-07-15_14-26-57

ปักหมุดพื้นที่ลงไป

firefox_2017-07-15_14-32-18

บางสถานการณ์ไม่ต้องการรูปหมุด ต้องการแค่วงรอบพื้นที่ ตั้งคาใหม่ด้วยการไม่ติ๊กที่ Draw Pin ดังรูปด้านลาง

python_2017-07-15_14-33-21

จะได้ผลลัพธ์บน Google Maps ดังนี้

firefox_2017-07-15_14-36-03

การตั้งค่าสำหรับ Google Earth

คล้ายๆ ตั้งค่าให้ Google Maps ที่ผ่านมา ดูรูปด้านล่าง

python_2017-07-15_14-40-42

ตัวอย่างการใช้งานขอใช้ทูลส์ Line Scale Factor 

python_2017-07-15_14-43-03

ปักหมุดลง Google Earth ป้อนชื่อไฟล์ก่อนแล้วจะสวิชท์เข้า Google Earth

googleearth_2017-07-15_14-47-59

การตั้งค่า (settings) จะเก็บไว้ที่ไฟล์ settings.xml ไฟล์นี้อยู่ที่โฟลเดอร์ “Appdata” ถ้าเป็นเครื่องผมเมื่อเปิดด้วยไอคอน “Example Data”

explorer_2017-07-15_14-52-19

ความจริงในงานสำรวจต้องการค่าพิกัดฉากที่ทศนิยมหลักที่ 3 ซึ่งเป็นหลักมิลลิเมตรและเพียงพอ ส่วนค่าพิกัดภูมิศาสตร์เช่นแล็ตติจูดและลองจิจูดจะละเอียดเทียบเท่าระดับมิลลิเมตร ต้องการทศนิยมมากกว่าตำแหน่งที่ 5 ขึ้นไปเช่น 23°48’23.78768″N อย่างไรก็ตาม Surveyor Pocket Tools เปิดโอกาสให้ตั้งได้ตามความต้องการของผู้ใช้ พบใหม่ในตอนต่อไปครับ

การออกแบบเส้นโครงแผนที่ความเพี้ยนต่ำ (Low Distortion Projection) ตอนที่ 1

ในตอนที่แล้วได้เกริ่นไปเรื่องเส้นโครงแผนที่ความเพี้ยนต่ำ ที่จะออกแบบประยุกต์มาใช้งานเพื่อให้ผู้ที่ออกแบบโครงการก่อสร้างบนระนาบพิกัดฉากตัวนี้สามารถทำได้ง่าย ไม่ต้องกังวลกับเรื่อง scale factor คือแบบที่ออกแบบบนระบบพิกัดฉากยาวเท่าไหร่เมื่อก่อสร้างแล้วไปวัดในสนามต้องได้เกือบเท่ากัน (แต่ต่างก้นน้อยมากๆ) และที่สำคัญที่สุดคือช่วงก่อสร้าง ช่างสำรวจสามารถวางผัง (Setting out หรือ Layout) โดยที่ไม่ต้องใช้สเกลแฟคเตอร์เข้ามาเกี่ยวข้อง เพราะสเกลแฟคเตอร์ที่ได้จากเส้นโครงแผนที่ความเพี้ยนต่ำจะมีค่าใกล้กับ 1.0 มากๆ จนสามารถละเลยไปได้

เครื่องมือช่วยในการออกแบบเส้นโครงแผนที่ต่ำ

ผมเขียนทูลส์ตัวเล็กๆไว้ชื่อ “Init Design LDP” อยู่ในชุด “Surveyor Pocket Tools” เหมือนเดิม ทูลส์ตัวนี้ตามชื่อครับ “Init Design” คือเป็นตัวช่วยในเบื้องต้น เพราะการออกแบบเส้นโครงแผนที่ต่ำ ต้องมีการลองผิดลองถูก (กลั่นและปรุงเพื่อให้ได้รสชาติที่ดีที่สุด) เพื่อเส้นโครงที่มีความเพี้ยนต่ำที่สุด ซึ่งจะให้ค่าสเกลแฟคเตอร์ที่ใกล้เคียงค่า 1.0 มากที่สุด แต่จะให้ใกล้เคียงค่า 1.0 แค่ไหนก็มีตัวแปรหลายตัวที่จะจำกัดความเป็นไปได้นี้

ทูลส์ตัวที่สองคือ “Create LDP” อยู่ในชุด “Surveyor Pocket Tools” เช่นเดียวกัน หลังจากได้เลือกเส้นโครงแผนที่สำหรับ LDP ได้แล้ว กำหนดจุดศูนย์กลางสำหรับ Central Meridian และสุดท้ายคำนวณค่า k0 ทูลส์ตัวนี้จะมาช่วยในการคำนวณหาค่าความเพี้ยน ตลอดจนทำการจัดเก็บค่าพารามิเตอร์เส้นโครงแผนที่ความเพี้ยนต่ำไว้ในฐานข้อมูล (LDP Database) หรือจะเรียกว่าตัวช่วยในการสร้างเส้นโครงแผนที่ก็พอได้

6 ขั้นตอนในการออกแบบ

1.กำหนดพื้นที่ขอบเขตและหาค่าตัวแทนความสูงเฉลี่ยเหนือทรงรี (h0)

กำหนดพื้นที่ขอบเขตของพื้นที่หรือบริเวณที่ต้องการใช้เส้นโครงแผนที่ความเพี้ยนต่ำหรือ LDP ส่วนใหญ่แล้วจะกำหนดให้เป็นสี่เหลี่ยมผืนผ้าคลุมพื้นที่ที่ต้องการใช้งาน เมื่อได้พื้นที่มาคร่าวๆแล้ว ต่อไปจะเลือกค่าความสูงเมื่อเทียบกับทรงรีเฉลี่ยของพื้นที่ (Average ellipsoidal height) ใช้สัญลักษณ์ h0 ย้ำอีกทีครับความสูงนี้ไม่ใช่ความสูงเที่ยบกับระดับน้ำทะเลปานกลาง (Orthometric height)  ถ้าพื้นที่มีค่าระดับเฉลี่ยไม่ต่างกันนักค่าความเพี้ยนจะมีค่าไม่มากนัก แต่ถ้าพื้นที่เป็นที่ราบติดภูเขาสูงแล้วต้องการ LDP  คลุมพื้นที่นี้ ในกรณีนี้จะได้ค่าความเพี้ยนที่สูงซึ่งไม่ดีนัก สำหรับ accuracy ความสูงทรงรีแต่ละจุดในพื้นที่ที่จะนำมาหาค่าเฉลี่ย ไม่จำเป็นต้องละเอียดมากแค่ ±6 เมตรก็เพียงพอ

ldp-h0
ไดอะแกรมแสดงเส้นโครงแผนที่ความเพี้ยนต่ำที่ระนาบพิกัดฉากสัมผัสที่ความสูงเฉลี่ยน h0

2.เลือกเส้นโครงแผนที่และกำหนด Central Meridian ที่จุดใกล้จุดศูนย์กลางพี้นที่

การเลือกเส้นโครงแผนที่ก็เลือกตามลักษณะของพื้นที่ ถ้าพื้นที่ยาวจากเหนือลงมาใต้ก็จะเลือกเส้นโครงแผนที่ Transverse Mercator (TM) ถ้าพื้นที่ยาวจากตะวันออกไปตะวันตกเลือกเส้นโครงแผนที่ Lambert Conformal Conic (LCC) หรือว่าถ้าพื้นที่เฉียงๆทะแยงๆก็เลือกเส้นโครงแผนที่ Oblique Mercator (OM) เมื่อเลือกเส้นโครงแผนที่ได้แล้ว ต่อไปคือหาจุดศูนยืกลางพื้นที่ (centroid) เพื่อวาง Central Meridian (CM) สำหรับเส้นโครงแผนที่ TM และ LCC ส่วนพื้นที่ที่ทะแยงจะวางเส้นโครงแผนที่ OM ก็เลือกสองจุดที่อยู่กลางๆพื้นที่เพื่อให้เส้น Initial line  ผ่าน เมื่อวางแล้วสามารถขยับออกไปซ้ายขวาได้ รายละเอียดมาว่ากันอีกทีในช่วงคำนวณ workshop

3.คำนวณหาค่าสเกลแฟคเตอร์ k0 ที่แกน Central Meridian

เมื่อได้ความสูงเฉลี่ยของพื้นที่ (Average ellipsoidal height) หรือ h0 มาแล้วจะนำมาคำนวณหาค่าสเกลแฟคเตอร์ (Axis Scale Factor) ที่แกนเของเส้นโครงแผนที่ ใชัสัญลักณ์ว่า k0 โดยคำนวณได้ดังนี้

จะเห็นว่าการคำนวณขั้นตอนแรกจะคำนวณหา RG ก่อนตามสูตรที่ 2 ซึ่งจะต้องมีพารามิเตอร์ของทรงรี a, e และค่าพิกัด latitude (φ) เมื่อได้ค่า RG แล้วนำค่าไปแทนหาค่า k0 ได้ดังสูตรแรก ค่า k0 ส่วนใหญ่แล้วเลือกมาใช้แค่ทศนิยมหกตำแหน่งก็พอแล้ว ขั้นตอนการคำนวณนี้เอง ผู้อ่านสามารถนำทูลส์ “Init Design LDP” มาช่วยได้ ซึ่งรายละเอียดจะได้กล่าวในภายหลัง

4.ตรวจสอบความเพี้ยนตลอดทั้งพื้นที่

เมื่อได้เส้นโครงแผนที่ความเพี้ยนต่ำมาแล้ว ก็จะเตรียมจุดในพื้นที่ที่จะนำมาหาค่าความเพี้ยน (distortion) ซึ่งใช้สัญลักษณ์ δ เมื่อคำนวณความเพี้ยนมาทุกจุด สามารถนำมาสร้างเส้นขั้นความสูง (contour) ได้ เพื่อหาชุดที่ค่าความเพี้ยนต่ำที่สุด

สูตรคำนวณค่าความเพี้ยนหาได้ดังสูตรด้านล่าง ค่า k คือ grid scale factor ของจุดที่คำนวณค่าได้ตามเส้นโครงแผนที่ที่เลือกมา

ถ้าได้ค่าเฉลี่ยความเพี้ยนที่ต่ำสุด แต่ยังได้ค่าที่ไม่ได้เกณฑ์ที่ตั้งไว้ กระบวนการคำนวณนี้จะเวียนกลับไปที่ข้อ 2 และข้อ 3 อีกครั้ง  โดยการขยับหา CM ไปด้านตะวันออกหรือด้านตะวันตก หรือขยับ latitude of origin ในกรณีเลือกใช้ TM หรือ standard parallel ในกรณีใช้ LCC ขึ้นไปทางทิศเหนือหรือขยับมาทางทิศใต้ ซึ่งจะมีผลทำให้ค่า k0 ที่ได้จากการคำนวณเปลี่ยนไปจากค่าเดิม จากนั้นทำการคำนวณหาค่าความเพี้ยนทั้งพื้นที่ใหม่อีกครั้ง

ในขั้นตอนนี้สามารถนำทูลส์ “Create LDP” มาช่วยได้ ซึ่งรายละเอียดการคำนวณที่ใช้ทูลส์มาช่วยจะได้กล่าวในรายละเอียดในหัวข้อถัดไป

5.กำหนดพารามิเตอร์เส้นโครงแผนที่ความเพี้ยนต่ำให้เรียบง่าย

ดังที่ผมกล่าวมาแล้ว ค่า k0  จะกำหนดไว้แค่ทศนิยมที่หกเท่านั้น การเปลี่ยนแปลงทศนิยมที่หกเทียบเท่ากับค่าความสูงเปลี่ยนไป 6.4 เมตรหรือประมาณ 1 ppm

การกำหนดค่า k0 ดังตัวอย่างเช่น k0 = 0.999997 หรือ k0 = 1.000012 ส่วนค่า latitude of origin หรือ standard parallel จะเลือกใช้ค่าที่เป็นจำนวนเต็มของลิปดาเช่น  latitude of origin = 23°47’N ส่วน central meridian ก็เช่นเดียวกันเช่น central meridian = 90°24’E

การกำหนดค่าพิกัดสำหรับจุดกำเนิดของระบบพิกัดฉาก (grid of origin) การกำหนดค่านี้ได้แก่ false easting และ false northing นั่นเอง การกำหนดที่นิยมค่าจะไม่เกินหลักแสนเพื่อไม่ให้ไปสับสนกับค่่าพิกัดในระบบ UTM/SPC และค่าพิกัดในพื้นที่ของเส้นโครงแผนที่ต้องไม่ติดลบ ตัวอย่างเช่น false northing = 200000 false easting = 100000

6.กำหนดหน่วยระยะทางและพื้นหลักฐานให้ชัดเจน

กำหนดหน่วยให้ชัดเจนเช่น Linear unit = metric และพื้นหลักฐานที่อ้างอิงเช่น Geometric reference system = WGS 1984

ออกแบบเส้นโครงแผนที่ความเพี้ยนต่ำสำหรับกรุงเทพมหานครและปริมณฑล

ก็ถือว่าเป็นกรณีศึกษาในเบื้องต้น ถ้าสมมติจะออกแบบเส้นโครงแผนที่ความเพี้ยนต่ำในพื้นที่ประมาณ 80 กม. x 90 กม. ระยะทางจากด้านเหนือไปทางใต้ประมาณ 90 กม. ระยะทางจะด้านตะวันออกไปด้านตะวันตกไม่เกิน 80 กม. ค่าความเพี้ยนที่มากสุดต่ำสุดควรจะเป็นเท่าไหร่ ข้อได้เปรียบที่คิดไว้ในใจสำหรับพื้นที่กรุงเทพมหานครและปริมณฑลคือค่าระดับเฉลี่ยค่อนข้างต่ำ ดังนั้นความเพี้ยนที่เกิดจากความสูงต่างไม่น่าจะมากนัก

Bangkok-Samutprakarn-Nonthaburi-Pathumthani

ก็ติดตามกันตอนต่อไปมาว่าเรื่องรายละเอียดตอนออกแบบตามวิธีการที่นำเสนอไป 6 ข้อดังกล่าวข้างต้น

เริ่มต้น Python ด้วย PySide2 + Miniconda + PyCharm

ขอนำเสนอวิธีการติดตั้งในเบื้องต้นเพื่อใช้ PySide2 สำหรับผู้ที่เริ่มต้นศึกษาเขียนไพทอน บางครั้งไม่รู้จะเริ่มต้นตรงไหน ผมจะขอแนะนำสามสหายที่จะมาช่วยทำให้ชีวิตง่ายขึ้น

  • PySide2 อยู่ระหว่างการพัฒนาจากเจ้าของ Qt framework เองจึงไม่ต้องห่วงว่าโครงการจะล้มลาเลิกร้างกันก่อน ฟรีและมีสัญญาอนุญาตแบบ LPGL v2 สามารถพัฒนาโปรแกรมเพื่อการค้าได้ รุ่นเสถียรอีกไม่นานนักน่าจะออกมาแล้ว
  • Miniconda3 เป็นส่วนหนึ่งของ Anaconda ซึ่งเป็นเฟรมเวิร์คสำหรับการทำ data science สำหรับภาษาไพทอน ส่วน Miniconda เป็นตัวจัดสภาพแวดล้อมให้ไพทอน เช่นถ้าต้องการพัฒนาโปรแกรมให้สามารถรันได้ทั้ง 32 บิตและ 64  บิต ก็ต้องตัวนี้เลย สามารถเลือกรุ่นของ Python ได้ด้วย อย่างเช่น PySide2 ต้องการไพทอน 3.5 สามารถกำหนดรุ่นของไพทอนได้ทีหลังด้วยการ config ที่ง่ายไม่กี่ขั้นตอน ฟรีแบบมีสัญญาอนุญาตแบบ BSD 3-clause
  • PyCharm สภาพแวดล้อมในการพัฒนาทั้ง editor, debugger ครบครัน ใช้งานสะดวก ง่าย ฟรีสำหรับรุ่น community ครั้งหนึ่งผมเคยชมว่า Eric ดีแต่ตัวนี้ดีกว่ามาก รุ่น community ฟรีมีสัญญาอนุญาตแบบ Apache license

ต่อไปจะลำดับความเรียงว่าต้องติดตั้งตัวไหนก่อนตัวไหนหลัง

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

ขณะที่เขียนอยู่นี้เวอร์ชั่น 3 ต้องการดาวน์โหลดก็ตามลิ๊งนี้ download miniconda ส่วนตัวผมเลือกรุ่น 64 บิตขนาดประมาณ 60 MB ติดตั้งก็ง่ายๆ เมื่อได้ไฟล์มาแล้วตอนติดตั้ง ช่วงที่ถามว่าจะเอาโปรแกรมไปเก็บไว้ที่ไหนผมขอแนะนำให้ไว้ตรงรากของไดรว์ C จะหาง่ายและสะดวกที่สุดตอนคลิกเข้าไปดูไฟล์

Miniconda3-latest-Windows-x86_64 (1)_2017-04-23_16-57-39

เมื่อติดตั้งเสร็จ เวลาใช้โปรแกรมนี้ใน windows 10 ใช้ปุ่มวินโดส์ (Win) บนคีย์บอร์ดแล้วพิมพ์คำว่า “anaconda” บางทีพิมพ์แค่คำว่า “ana” ก็จะเห็น shortcut “Anaconda Prompt” ขึ้นมาก็ enter เข้าได้เลย จะแสดงพาทที่ติดตั้งไว้ด้วยเครื่องผมคือ “C:\Miniconda3” ลองเรียก python ดูจะเห็นว่าเป็นรุ่น 3.6.0

cmd_2017-04-24_04-54-34

สมมติว่าผมต้องการติดตั้ง python รุ่น 3.5 เพราะว่า Pyside2 ต้องการรุ่นนี้ และผมต้องการพัฒนาโปรแกรมด้วย PySide2 ทั้ง 32 bit และ 64 bit ที่คอมมานด์ พร็อมป์จัดการพิมพ์ตามนี้

set CONDA_FORCE_32BIT=1
conda create -n py35_32 python=3.5

คำสั่งแรกบังคับให้ miniconda อยู่ในโหมด 32 บิต คำสั่งถัดไปสร้างสภาพแวดล้อมชื่อ py35_32 แล้ว Miniconda จะดาวน์โหลดไลบรารีที่เกี่ยวข้องมาติดตั้งให้ทันที โดยในที่นี้ก็คือ Python รุ่น 3.5  เวลาต้องการใช้งานสภาพแวดล้อมนี้ใช้คำสั่งนี้

activate py35_32

จะได้ผลลัพธิ์ดังนี้ จะเห็นตัวหนังสือด้านหน้าจะเปลี่ยนเป็น “py35_32”

cmd_2017-04-23_18-47-30

ต่อไปสร้างสภาวะแวดล้อมเป็นไพทอน 64 บิต คำสั่งแรกปลดล็อคการใช้โหมด 32 บิต และคำสั่งที่สองสร้างสภาวะแวดล้อมชื่อ “py35_64” ระบุไปว่าเราต้องการไพทอนรุ่น 3.5

set CONDA_FORCE_32BIT=
conda create -n py35_64 python=3.5

เวลาใข้งานต้องการจะใช้ในโหมดนี้ ใช้คำสั่ง

activate py35_64

ถ้าดูโฟลเดอร์ของ miniconda จะเห็นว่ามีการสร้างโฟลเดอร์ให้สองโฟลเดอร์ ตามที่เราสร้างด้วยคำสั่ง create

explorer_2017-04-24_04-58-04

ติดตั้งไลบรารีบนสภาพแวดล้อม 32 บิต

ต่อไปจะติดตั้งไลบรารีต่างๆ ซึ่งก็แล้วแต่ละคนที่จำเป็นต้องใช้ ตัวอย่างที่ผมต้องการใช้งานได้แก่ openpyxl, pyproj, geographiclib, pyshp, gmplot, simplekml และที่สำคัญที่สุดคือ pyside2 เนื่องจากไลบรารี openpyxl บางครั้งผมติดตั้งจากคำสั่ง pip และไลบรารีตัวนี้ต้องการ lxml บางครั้งติดตั้งแล้วใช้งานไม่ได้ ต้องไปดาวน์โหลดไฟล์ wheel มาติดตั้งเอง หมายเหตุว่าไฟล์ wheel คุณสมบัติพิเศษของมันคือสามารถแนบไฟล์ที่ต้องการใช้มาได้ด้วยเช่นไฟล์ dll

ต่อไปจะติดตั้งไลบรารีในโหมด 32 บิตก่อน อย่าลืมใช้คำสั่ง

activate py35_32

ติดตั้ง lxml ด้วยการไปดาวน์โหลดไฟล์ wheel ที่เว็บนี้มีเกือบทุกอย่างที่ต้องการ download python extension packages ไปถึงแล้วก็ค้นหาในเว็บหา lxml เจอแล้วก็ดาวน์โหลดมาทั้ง 32 บิตและ 64 บิต และไฟล์ต้องมีคำว่า cp35 ด้วยเป็นการระบุว่าใช้กับ python 3.5

firefox_2017-04-24_14-12-55

pip install lxml‑3.7.3‑cp35‑cp35m‑win32.whl

ต่อไปติดตั้งไลบรารีของไพทอนตัวที่เหลือ

pip install openpyxl

pip install geographiclib

pip install pyshp

pip install pyproj

pip install gmplot

pip install simplekml

ติดตั้ง PySide2 ด้วย wheel

เนื่องจาก PySide2 เวอร์ชั่นจริงยังไม่ได้ออกมา ดังนั้นต้องอาศัยไฟล์ wheel หรืออีกทางหนึ่งก็คือไปดึงไฟล์โค๊ดจาก github มาคอมไพล์เอง แต่ผมทำไม่สำเร็จ เลยต้องอาศัยวิธีนี้ ดาวน์โหลดไฟล์ wheel ได้จากลิ๊งค์นี้ download pyside2

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

firefox_2017-04-24_07-35-19

ใช้ command prompt ของ miniconda ทำการ cd เข้าไปหาไฟล์ที่เก็บไว้แล้วติดตั้งด้วย pip

pip install  PySide2-2.0.0.dev0-cp35-cp35m-win32.whl

สำหรับเครื่องคอมพิวเตอร์ผม ตอนติดตั้ง PySide2 ดูจากรูปด้านล่าง

cmd_2017-04-24_07-41-40

ติดตั้งไลบรารีบนสภาพแวดล้อม 64 บิต

เหมือนกับติดตั้งให้ 32 บิต เพียงแต่ตอนติดตั้ง PySide2 ใช้ไฟล์ wheel คนละตัว เริ่มด้วยคำสั่งนี้ก่อน

activate py35_64

จากนั้นก็ใส่ยาวเลย

pip install lxml‑3.7.3‑cp35‑cp35m‑win_amd64.whl

pip install openpyxl

pip install geographiclib

pip install pyshp

conda install pyproj

pip install gmplot

pip install simplekml

pip install PySide2-2.0.0.dev0-cp35-cp35m-win_amd64.whl

ลองทดสอบเรียกไพทอน แล้ว import ไลบรารีเหล่านี้ดูกัน

cmd_2017-04-24_07-52-59

ผ่านฉลุยครับไม่มี error เลย

ติดตั้ง PyCharm

ต่อไปจะดาวน์โหลด PyCharm รุ่น Community ได้ตามลิ๊งค์นี้ download pycharm เลือกเอารุ่น community ซึ่งจะฟรี ได้ไฟล์มาแล้วก็ทำการติดตั้งจากไฟล์ installer ที่ดาวน์โหลดมา เมื่อติดตั้งเสร็จแล้ว ที่สำคัญคือจะ config ให้ pycharm รู้จัก python ว่าต้องใช้สภาพแวดล้อมไหนอย่างไร รัน Pycharm ครั้งแรกถ้ายังไม่มีไฟล์ project เลยเรียกเมนู File > New Project… แล้วใส่ชื่อโครงการ และที่สำคัญคือสามารถเลือกสภาวะแวดล้อมได้ ผมเลือก “py35_64”

pycharm64_2017-04-24_08-05-09

เมื่อเข้าไปแล้วโครงการยังว่าง ผมสร้างไฟล์ใหม่ ด้วยคำสั่ง File > New… เลือกเป็น python file จะเห็นหน้าตาโล่งๆ ว่างปล่าว

pycharm64_2017-04-24_08-11-26

แต่ถ้าต้องการเปลี่ยนเป็นสภาวะแวดล้อม “py35_32” ในโหมด 32 บิต จะต้องไปที่เมนู File > Settings… จะเห็นไดอะล็อกที่สามารถเปลี่ยนได้ที่ Project Intepreter

pycharm64_2017-04-24_08-12-59

โปรแกรมตัวอย่างทดสอบ PySide2

มาลอง PySide2 กันครับ ตรงหน้าว่างๆของ test.py ลองพิมพ์ตัวอย่างนี้เข้าไป หรือ copy & paste ไปก็ได้ แต่มือใหม่ควรจะหัดพิมพ์ สำหรับโปรแกรมมิ่งจะไม่อธิบายนะครับ แต่ผมแนะนำว่าให้หาอ่านจาก PyQt5 ดีกว่าเพราะ tutorial เยอะมาก

class=”code”># -*- coding: utf-8 -*-

import sys
from PySide2.QtWidgets import QApplication, QWidget

if __name__ == ‘__main__’:

app = QApplication(sys.argv)

w = QWidget()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle(‘Simple’)
w.show()

sys.exit(app.exec_())

จะทำการรันดู ที่เมนู Run > Run… เลือกไฟล์ test.py  จะได้ผลลัพธ์คือ error ไม่มีอะไรครับ PyCharm  หาไลบรารีของ PySide2 ไม่เจอ

pycharm64_2017-04-24_08-23-45

มีวิธีหลายวิธีตั้งแต่ environment variables  ให้วินโดส์ วิธีแรกที่จะแนะนำนั้นง่ายเพิ่มโค๊ดประมาณ 5 บรรทัด

# -*- coding: utf-8 -*-
import sys
import os
import PySide2

dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', '')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path

from PySide2.QtWidgets import QApplication, QWidget

if __name__ == '__main__':
    app = QApplication(sys.argv)

    w = QWidget()
    w.resize(250, 150)
    w.move(300, 300)
    w.setWindowTitle('Hello world')
    w.show()

    sys.exit(app.exec_())

โดยเฉพาะบรรทัดที่ dirname = os.path.dirname(PySide2.__file__) จะเป็นการดึงเอาที่อยู่ของไดเรคทอรีหรือโฟลเดอร์ของ PySide2 ที่่เราใช้งานอยู่ ไม่ว่าเรากำลังใช้ 32 บิตหรือ 64 บิต ก็ไม่มีปัญหา ลองรันดูที่นี้ผ่านครับ

pycharm64_2017-04-24_14-39-17

วิธีที่สองใช้วิธีเขียน Environment Variables ที่คีย์บอร์ดกดคีย์ win  ค้างไว้กด x (Win + x) จะมีเมนูของวินโดส์เลือก System แล้วเลือก Advance system settings จากนั้นจะมีไดอะล็อกอีกตัวขึ้นมา คลิกเลือก Environment Variables

SystemPropertiesAdvanced_2017-04-24_17-19-09

คลิก New… ที่ System Variables ถ้าเครื่องผมป้อนดังรูปด้านล่าง สำหรับสภาพแวดล้อม 64 บิต ถ้า 32 บิต แก้ตัวหนังสือ py35_64 เป็น py35_32

SystemPropertiesAdvanced_2017-04-24_17-21-36

วิธีนี้อานุภาพรุนแรงครับ เพราะว่าโปรแกรมบางโปรแกรมที่เขียนด้วย PyQt5 หรือ Qt5 ที่ติดตั้งไว้บนเครื่องคอมพิวเตอร์จะพร้อมใจกัน error เพราะหาไลบรารีของ  Qt5 ไม่เจอ เพราะ system variable ตัวนี้ QT_QPA_PLATFORM_PLUGIN_PATH สิทธิ์จะมากที่สุด ปกติโปรแกรมที่พัฒนาด้วย Qt จะเขียนไฟล์ qt.conf ไว้เพื่อระบุว่าไฟล์ไลบรารีของ Qt อยู่ที่ไหน โดยที่ไฟล์ qt.conf ก็วางที่เดียวกับ execute file แต่ถ้าเจอ system variable ตัวนี้จะไม่อ่านไฟล์ qt.conf ก่อน แต่จะวิ่งมาที่พาธที่เราตั้งนี้

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