หลังจากใน MapOnMobile ภาคแรก ผมได้แนะนำถึงหลักการต่างๆ รวมไปถึงสูตรคำนวณแล้ว ผมเชื่อว่าด้วยข้อมูลพวกนั้น หลายๆ คน อาจจะไปทำ Map มาสำเร็จแล้วก็ได้ แต่ไม่เป็นไรครับ ในตอนที่สองนี้ ผมจะมาแนะนำถึงเรื่องการวาดแผนที่ก็แล้วกันครับ :)
การวาดแผนที่
ผมขออ้างอิงจากภาพเก่าในภาคแรกมานิดนึงนะครับ สมมุติว่า เราต้องการจะวาดภาพของแผนที่ ในระดับ 2 ซึ่งมีแผนที่ทั้งหมด 16 แผ่น (4 แถว 4 คอลัมน์) ถ้าลองมองข้ามตัวเลขไป มันก็คือการไล่สร้างตาราง จากซ้ายไปขวา บนลงล่างธรรมดานั่นเอง ซึ่งโค๊ด ก็คงหนีไม่พ้น For ซ้อนกัน 2 For แบบนี้
for (int row = 0; row < 4; row++)
{
for (int column = 0; column < 4; column++)
{
// Draw
}
}
แต่ปัญหาก็คือ จะวาดยังไง และเอาแผนที่มาจากไหน มากกว่า จริงไหมครับ :)
ถ้าเราลืมเรื่องการคำนวณไปก่อน วาดแผนที่มันทั้งโลกเลย ที่ Level 1 (ซึ่งจะมีแค่ 4 แผ่น) URL ของแผนที่ทั้ง 4 ก็คือ
http://a2.ortho.tiles.virtualearth.net/tiles/a0.jpeg?g=45
http://a2.ortho.tiles.virtualearth.net/tiles/a1.jpeg?g=45
http://a2.ortho.tiles.virtualearth.net/tiles/a2.jpeg?g=45
http://a2.ortho.tiles.virtualearth.net/tiles/a3.jpeg?g=45
(a2 ด้านหน้าคือ Server นะครับ)
อย่างแรกเลย คือ การวาดภาพนั้น มีเรื่องของ Performance เข้ามีเกี่ยวข้อง ถ้าเราวาดภาพไป อ่านไฟล์ไป คงไม่ดีแน่ ดังนั้นแล้ว เราควรจะมี Cache เพื่อเก็บภาพไว้ในแรมก่อนครับ และสำหรับการแสดงภาพแผนที่ เราก็ต้องดาวน์โหลดมาด้วย ซึ่งเน็ต 2.5G บ้านเราก็ใช่ว่าจะเร็ว ยังไงซะ ก้อต้องมีที่เซฟภาพไว้ก่อนจะเอาขึ้นมาวาด ดังนั้น ปัญหาแรกเลย คือ เซฟภาพที่ไหน…
โค๊ดด้านล่างนี้ ใช้หาว่า โฟลเดอร์ปัจจุบัน ที่โปรแกรมเรากำลังรันอยู่เนี่ย มันโฟลเดอร์อะไร (ซึ่งเป็นหนึ่งในคำถามยอดฮิตที่พบบ่อยมาก) และผมต่อท้ายให้เป็น Folder ชื่อ Cache ด้วย จะได้เป็นระเบียบ (จริงๆ ควรจะใส่ SD Card นะ…) อ้อ โค๊ดนี้ของ Compact Framework นะครับ ของ Full Framework ใช้ AppDomain.CurrentDomain.BaseDirectory ได้เลย
// Cache Folder
private string _CachePath = Path.GetDirectoryName(
Assembly.GetExecutingAssembly().GetName().CodeBase) + @"\Cache\";
จากนั้น ก็ต้องมีฟังก์ชั่นสำหรับให้เราดึงภาพได้ แต่ว่าภาพจะโดนอ่านจากไฟล์ในครั้งแรก และเราจะเก็บในแรมไว้ สำหรับครั้งต่อไป
สำหรับผู้อ่านตาดี คงจะสังเกตเห็นแล้วว่า เอ๊ะ ทำไม Dictionary มันถึงเป็น WeakReference แทนที่จะเป็น Bitmap…
นั่นก็เพราะว่า ถ้าเราใส่ Bitmap ไปใน Dictionary เลย จะเป็นการสร้าง Strong Reference (ซึ่งตรงข้ามกับ Weak Reference) เข้ากับ Bitmap ครับ ซึ่งถ้านึกภาพของการใช้งาน เวลาเราซูมเข้า ซูมออก ก็จะมีแผ่นแผนที่จำนวนมาก ที่เราไม่ต้องการใช้แล้ว ถ้าเราใช้ Weak Reference ก็จะมีค่าเท่ากับการที่เรา ไม่ได้ถือ Reference ไปยัง Object นั้นไว้จริงๆ ทำให้ CLR สามารถนับ Bitmap ที่ถูกทิ้งพวกนั้นเป็นขยะได้เอง โดยที่เราไม่ต้องจัดการ Remove ออกจาก Dictionary เองครับ
ส่วนการวาด เราจะใช้การ Override ฟังก์ชั่น OnPaintBackground ของคอนโทรลครับ
จะเห็นว่า โค๊ดในการวาด ก็คือวาดตามแถว ตามหลักกันดื้อๆ เลยนั่นละครับ แต่ที่ Advance กว่าหน่อย ก็คือ เราทำการแปลแถว และหลัก มาเป็น Quad Key สำหรับ Virtual Earth แล้วก็ขอภาพจาก Cache เอาออกมาวาดนั่นเองครับ ผลลัพทธ์ที่ได้ ก็ตามภาพครับ :)
เจาะช่อง มองแผนที่ (ViewPort)
แน่นอนว่า นี่แค่ Level 1 แผนที่มันเองก็ทะลุจอไปไกลแล้ว (ยกเว้นคนใช้ Touch HD) นั่นก็เพราะว่า แผนที่ 1 แผ่น ก็มีขนาด 256x256 Pixel ต่อกัน 4 แผ่น ก็เข้าไป 512x512 แล้ว ทั้งที่จอเราเองก็มีกันแค่ 240 Pixel เป็นอย่างมาก เราจึงต้องอาศัยแนวคิดของ Viewport มาช่วยครับ
ลองนึกภาพถึง เรามีหน้าต่างกรอบสีส้มอยู่บนผนัง (ที่ขยับได้) ที่เราใช้มองไปยังวิวภายนอกที่เป็นแผ่นสีฟ้า ถ้าเราอยากเปลี่ยนวิว ไปมองส่วนอื่นบ้าง โดยที่เรายืนมองจากตำแหน่งเดิม ไม่ก้าวไปข้างหน้า หรือถอยหลัง (มองแบบ 2 มิตินั่นเอง) เราก็สามารถทำได้โดยการ ขยับหน้าต่างไปมาตามที่เราต้องการ เพื่อจะมองส่วนอื่นๆ บ้าง
แล้วเราขยับหน้าต่างไม่ได้ละ?…เราก็ขยับวิวภายนอกสิครับ :)
ปกติแล้ว เวลาเราวาดภาพด้วยการเขียนโปรแกรม จุด Origin (0.0) ก็จะอยู่ที่มุมบนซ้ายเสมอ ถ้าเราเริ่มวาดแผนที่แผ่นแรกที่ตำแหน่ง (0, 0) ซึ่งได้จากสูตร column x 256, row x 256 วิวของเราก็จะเริ่มที่ Origin ในเมื่อเราอยากจะ ขยับวิว เราก็แค่เปลี่ยนจุด Origin เท่านั้นเองครับ ซึ่งการทำแบบนี้ เรียกว่า Translation (หรือ Translate Transform ใน WPF/Silverlight นั่นเอง) และตามการใช้งานส่วนมาก เรามักจะให้จุดสนใจอยู่ที่กลาง Viewport ครับ ดังนั้น Translation ของเราก็ควรจะทำตามหลักการนี้ด้วย ดังโค๊ดดัานล่างนี้ครับ
จากโค๊ด จะเห็นว่า ผมทำการ ย้ายแกน โดยย้ายจุดสนใจไปในทางลบ ให้มันชิดมุมซ้ายบนเลย จากนั้น ก็ค่อยขยับมันกลับมาตรงกลางมุมมองอีกที จากการทดสอบกับจุดกรุงเทพ ที่ผมคำนวณให้ดูไว้ในตอนแรก ก็ได้ผลดังภาพครับ
นอกจากนี้ ไม่ว่าจะเป็นการวาดจุดต่างๆ บนแผนที่ ก็สามารถใช้หลักการเดียวกันได้ครับ เช่น ถ้าผมต้องการวาดจุดสีแดง แสดงตำแหน่งของกรุงเทพ ผมก็ทำการคำนวณตำแหน่งของกรุงเทพ ออกมาเป็น Pixel ก่อน แล้วค่อยย้ายแกน เพื่อขยับมันมาอยู่ตรงกลาง
ที้นี้ ก็ใกล้ความจริงมากแล้วละครับ จะวาดแผนที่ก็ได้แล้ว วาดจุดก็ได้แล้ว ยังจะขาดก็แต่ ทำให้แผนที่มันลากได้เนี่ยละ ซึ่งเดี๋ยวผมขออุบไว้ก่อน ว่าจะทำยังไง อีกแป๊บผมจะมา Update เฉลยให้ครับ
ขั้นต่อไป?
สำหรับตอนต่อไป ก็จะต้องเป็นการทำให้โปรแกรม สามารถดาวน์โหลดภาพ ในระหว่างที่เรากำลังเล่นโปรแกรมอยู่ ได้โดยที่โปรแกรมไม่ค้าง ซึ่งต้องใช้เทคนิคเรื่อง Threading เข้ามาช่วย รวมไปถึงการคำนวณแผนที่ใหม่ ซึ่งจะเปลี่ยนไปเวลาที่เราขยับดูแผนที่ ทั้งสองหัวข้อนี้ผมจะมาแนะนำในตอนหน้าครับ และหลังจากเราสร้างให้มันใช้งานได้แล้ว ค่อยเข้าเรื่องการ Refactor มันให้มีโครงร่างสวยๆ ใช้งานง่ายๆ กันต่อครับ