MapConductor คืออะไร — ทำให้เลือก SDK แผนที่ทีหลังได้

SDK โอเพนซอร์สที่นำ SDK แผนที่รายใหญ่มาไว้หลัง API ร่วมชุดเดียว สรุปว่าทำอะไรและมีประโยชน์เมื่อใด

MapConductor คือ SDK โอเพนซอร์สสำหรับใช้งาน SDK แผนที่ผ่าน API ร่วมชุดเดียว มีให้ใช้บน Android (Jetpack Compose), iOS (SwiftUI) และ React (TypeScript) ปัจจุบันรองรับผู้ให้บริการ 15 ราย ใช้สัญญาอนุญาตแบบ Apache-2.0

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

SDK แผนที่ทุกเจ้าล้วนทำมาดี

Google Maps, Mapbox, HERE, ArcGIS, MapLibre, MapTiler, TomTom, Longdo, Mappls — SDK แผนที่แต่ละเจ้าผ่านการขัดเกลามายาวนานและมีจุดแข็งของตัวเอง ทั้งความละเอียดของถนนและที่อยู่ การนำทาง การเชื่อมต่อกับข้อมูล GIS อิสระในการออกแบบ และพื้นที่ที่รองรับ คำถามว่า “เจ้าไหนดีที่สุด” ไม่มีคำตอบกลาง เพราะขึ้นกับแอปที่สร้างและพื้นที่ที่ให้บริการ

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

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

ทำอะไร

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

kotlin
// ประกาศโอเวอร์เลย์ไว้นอกเงื่อนไข สลับผู้ให้บริการแล้วก็ไม่ต้องเขียนใหม่
val overlays: @Composable MapViewScope.() -> Unit = {
    Marker(markerState)
    Polyline(routeState)
}

if (useMapLibre) {
    MapLibreMapView(state = maplibre, content = overlays)
} else {
    GoogleMapView(state = googlemaps, content = overlays)
}

จะวางแผนที่สองอันไว้บนจอเดียวกันแล้วสลับตอนรันก็ได้ ถ้าส่งตำแหน่งกล้องล่าสุดให้ฝั่งที่สลับไป ภาพก็จะไม่กระโดด

สิ่งที่ตรงกันไม่ใช่แค่วิธีเขียน

เวลาได้ยินคำว่า “API ร่วม” สิ่งที่น่ากังวลที่สุดคงเป็นการที่วิธีเขียนตรงกันแต่ผลลัพธ์ไม่ตรงกัน เช่น ซูม 14 มองเห็นไม่เท่ากันในแต่ละสภาพแวดล้อม หรือระยะทางระหว่างสองจุดไม่ตรงกันระหว่าง Android กับ iOS ซึ่งเกิดขึ้นจริง

MapConductor ถือส่วนนั้นไว้เองในฝั่ง SDK

สิ่งที่เรามีเองสิ่งที่ตรงกันเพราะแบบนั้น
การคำนวณเชิงภูมิศาสตร์ระยะทาง ทิศ พื้นที่ และการประมาณค่า ใช้สูตรเดียวกันทั้งใน Kotlin, Swift และ TypeScript เมื่อไม่พึ่งยูทิลิตีของ SDK ตัวเลขที่แสดงจึงไม่เพี้ยนข้ามแพลตฟอร์ม
กล้องแปลงไปมาระหว่างซูมกับความสูงกล้อง และปรับเทียบจากการวัดจริง ซูม 14 จึงกว้างเท่ากันทุกผู้ให้บริการ
การวาดมาร์กเกอร์ถ้ามีไม่มากใช้มาร์กเกอร์เนทีฟ เกิน 2,000 จุดจะสลับเป็นไทล์ราสเตอร์ภายในโดยอัตโนมัติ แม้มีหลายหมื่นจุดการขยับกล้องก็ยังลื่น และโค้ดฝั่งแอปไม่ต้องเปลี่ยนแม้แต่บรรทัดเดียว
รูปทรงผู้ให้บริการที่ไม่มีฟีเจอร์แบบเนทีฟ เช่น โพลีกอนแบบมีรู หรือเส้นวงกลมใหญ่ เราจะวาดเป็นไทล์ราสเตอร์ให้ได้หน้าตาเหมือนกัน
การแสดงที่มากฎการสลับข้อความแสดงที่มาตามระดับซูมและพื้นที่ที่มองเห็น ถือเป็นส่วนหนึ่งของดีไซน์แผนที่

ถ้าเป็นเพียงตัวห่อที่เรียกเมธอดของแต่ละ SDK ต่อไป สิ่งที่ทำได้จะถูกลดลงมาเท่ากับ SDK ที่มีฟีเจอร์น้อยที่สุด เราจึงเลือกแนวทางวาดเองเพื่อเติมส่วนที่ขาด

มีประโยชน์อย่างไร

จำ API ชุดเดียวก็พอ

SDK แผนที่แต่ละเจ้ามีแนวคิดและการตั้งชื่อต่างกัน ทุกครั้งที่เปลี่ยนผู้ให้บริการหรือย้ายแพลตฟอร์มจึงต้องเรียนรู้ใหม่ ส่วน MapConductor จำเพียง API ชุดเดียว และชื่อกับความหมายเหมือนกันทั้งบน Android, iOS และ React สิ่งที่ได้เรียนรู้บน Android ใช้ต่อบน iOS ได้ทันที และเมื่อมีคนใหม่เข้าทีม สิ่งที่ต้องอ่านก็ไม่เพิ่มขึ้นตามจำนวนผู้ให้บริการ

เขียนโค้ดที่ไม่มีชื่อ SDK แผนที่โผล่มาได้

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

คุยเรื่องสเปกครั้งเดียวจบ

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

เราอยากสร้างสภาพที่ “เลือกได้”

ที่เล่ามาทั้งหมดเป็นมุมของคนที่นำแผนที่ไปใช้ แต่สิ่งที่ MapConductor อยากทำไม่ได้มีเพียงเท่านั้น

ทุกวันนี้ การเลือก SDK แผนที่กลายเป็นคอมมิตแรกของการพัฒนา การคัดเลือกจึงไม่ใช่ “ลองแล้วค่อยตัดสิน” แต่เป็น “ตัดสินก่อนแล้วค่อยสร้าง” และการเปรียบเทียบก็จบอยู่บนกระดาษ สภาพแบบนี้ไม่ดีต่อฝั่งที่ถูกเลือกเช่นกัน เพราะ SDK ที่ดีอาจหลุดจากรายชื่อ ไม่ใช่เพราะคุณภาพ แต่เพราะ “เขียนด้วยอีกเจ้าไปแล้ว”

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

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

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

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

อนึ่ง การปฏิบัติตามเงื่อนไขการใช้งานของข้อมูลและบริการเป็นความรับผิดชอบของนักพัฒนาที่นำไปใช้ การที่ประกอบเข้าด้วยกันได้ในทางเทคนิค กับการที่การประกอบนั้นได้รับอนุญาต เป็นคนละเรื่องกัน

กรณีที่เหมาะและไม่เหมาะ

เหมาะกับกรณีเหล่านี้

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

ในทางกลับกัน ไม่เหมาะกับกรณีเหล่านี้

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

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

เริ่มต้นอย่างไร

หากใช้ MapLibre จะทำงานได้โดยไม่ต้องมีคีย์ API กว่าจะได้แผนที่แรกใช้เวลาราวห้านาที

เราเผยแพร่ความคืบหน้าการพัฒนาและข้อมูลรุ่นที่ปล่อยไว้บน Discord ยินดีรับทั้งความเห็นเรื่องการออกแบบและรายงานข้อบกพร่อง โค้ดอยู่ที่ GitHub