เอกสาร / แนวคิด / สถาปัตยกรรม

ภาพรวมสถาปัตยกรรม

MapConductor เป็นสะพานเชื่อมระหว่างแอปพลิเคชันและ SDK แผนที่ต่างๆ แอปพลิเคชันเรียกใช้เพียง API แผนที่ที่รวมกัน และ MapConductor จะส่งต่อการประมวลผลไปยังผู้ให้บริการที่เลือก บน Android (Kotlin + Compose), iOS (Swift + SwiftUI) และ React (Web) การแบ่งแพ็กเกจและการแบ่งหน้าที่ภายในมีการออกแบบเหมือนกัน

ANDROID
android-sdk-core
iOS
ios-sdk-core
REACT
js-sdk-core / js-sdk-react

01 · ภาพโดยรวม

ขั้นตอนการประมวลผลจะเหมือนกันในทุกแพลตฟอร์ม: แอปพลิเคชัน → API แผนที่ที่รวมกัน → Core → ไดรเวอร์ SDK แผนที่ต่างๆ → SDK แผนที่ต่างๆ เพียง 2 ชั้นบนสุดเท่านั้นที่เป็นโค้ดที่แอปพลิเคชันเขียน ส่วน 2 ชั้นล่างสุดสามารถเปลี่ยนแทนได้

APP

UI และตรรกะทางธุรกิจ

React · Compose · SwiftUI
UNIFIED API

API แผนที่แบบรวมศูนย์

MapViewState · Marker · Camera
CORE

ฟีเจอร์หลัก (แผนที่ มาร์กเกอร์ รูปทรง อีเวนต์)

Manager · Controller · Overlay
DRIVER

ไดรเวอร์ของผู้ให้บริการ

*-for-googlemaps / -maplibre / …
MAP SDK

SDK แผนที่ (เนทีฟ / JavaScript)

Google Maps · MapLibre · Mapbox · MapKit · ArcGIS · HERE …

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

แพลตฟอร์ม

02 · การกำหนดค่าตามแพลตฟอร์ม

Core และไดรเวอร์แยกเป็นแพ็กเกจ แอปพลิเคชันจะเพิ่มเฉพาะ Core และไดรเวอร์ของผู้ให้บริการที่ใช้เป็น dependency เท่านั้น

android-sdk-core มีโมเดลที่ใช้ร่วมกันและการใช้ความแตกต่าง ในขณะที่ android-sdk-compose มอบที่เก็บสถานะสำหรับ Compose GeoJSON, การจัดกลุ่ม, และแผนที่ความร้อนแยกเป็นโมดูลเพิ่มเติม (android-geo-layer / android-marker-clustering / android-heatmap)

03 · องค์ประกอบของเลเยอร์

เพื่อให้นำกลับมาใช้ซ้ำข้ามแพลตฟอร์มได้ หน้าที่ถูกแบ่งออกเป็น 6 เลเยอร์ เลเยอร์บนไม่ทราบถึงเอนทิตีของเลเยอร์ล่าง

#
เลเยอร์
บทบาท
1
เฟรมเวิร์ก UI
UI แบบประกาศที่แอปพลิเคชันใช้ เช่น React / Vue / Jetpack Compose / SwiftUI ฯลฯ
2
API แผนที่แบบรวมศูนย์
API ของ MapConductor ที่จัดการ Marker / Shape / Camera และ Event ด้วยแนวคิดทั่วไป โค้ดที่แอปพลิเคชันเขียนขึ้นจริงจะถูกเขียนสำหรับสิ่งนี้
3
สะพานเชื่อม
ส่งต่อการดำเนินการของ API แบบรวมศูนย์ลงไปยังระดับล่างตามสภาพแวดล้อมการทำงาน ไม่จำเป็นหาก UI และการเรนเดอร์เสร็จสมบูรณ์ในสภาพแวดล้อมการทำงานเดียวกัน จะเข้ามาแทรกแซงเฉพาะเมื่อทั้งสองอยู่ในสภาพแวดล้อมการทำงานที่แยกจากกัน
4
เลเยอร์ข้ามแพลตฟอร์ม
วิธีการฝังและวิธีการประกาศของ SDK แผนที่เนทีฟแตกต่างกันไปในแต่ละแพลตฟอร์มเฉพาะในเลเยอร์นี้เท่านั้น
5
SDK เนทีฟ / ไดรเวอร์
เลเยอร์ที่รับผิดชอบการเรนเดอร์ Camera / Style และ Overlay จริง (Native สำหรับ Android / iOS หรือไดรเวอร์ JS บน Web)
6
SDK แผนที่
เอนทิตีจริง เช่น Google Maps / MapKit / MapLibre / Mapbox / ArcGIS / HERE ฯลฯ

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

ในกรณีของ React Native

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

จุดที่โค้ดเดียวกันแตกแขนง
SHARED

โค้ดที่แอปเขียน

js-sdk-core · js-sdk-react
WEB

React

DRIVER
ไดรเวอร์ JavaScript
react-for-*
ไม่ต้องมีบริดจ์ (รันไทม์เดียวกัน)
MAP SDK
SDK แผนที่แบบ JavaScript
Google Maps · MapLibre · Leaflet · Cesium …
REACT NATIVE

React Native

DRIVER
ไดรเวอร์ที่ส่งต่อไปยังเนทีฟ
reactnative-for-*
BRIDGE
โมดูลเนทีฟของ RN
เลเยอร์ 3
NATIVE SDK
MapConductor SDK เนทีฟ → SDK แผนที่
Google Maps · MapLibre · ArcGIS · HERE

เลเยอร์ 3 ในตารางด้านบนคือบริดจ์ บน Web จะไม่มีส่วนนี้เพราะ UI และการเรนเดอร์เสร็จสมบูรณ์ในสภาพแวดล้อมรันไทม์เดียวกัน ใน React Native ต้องมีส่วนนี้เพราะทั้งสองแยกกันอยู่ในสภาพแวดล้อมรันไทม์ต่างกัน เฉพาะเลเยอร์นี้เท่านั้นที่แตกต่างตามแพลตฟอร์ม ส่วนด้านบนไม่เปลี่ยนแปลง

ผู้ให้บริการที่ใช้งานได้กับ React Native มีอยู่ 4 ราย คือ Google Maps, MapLibre, ArcGIS และ HERE เนื่องจากจำกัดเฉพาะรายที่มีโมดูลที่สอดคล้องกันในฝั่ง Native จึงมีจำนวนน้อยกว่าผู้ให้บริการ 13 รายบน Web

04 · โครงสร้างภายในของ Core

Core มีโครงสร้างซ้ำในรูปแบบของบทบาททั้ง 7 อย่างเดียวกันสำหรับแต่ละองค์ประกอบ เช่น เครื่องหมาย, เส้นโค้ง, รูปหลายเหลี่ยม, วงกลม, ภาพพื้นดิน และเลเยอร์แรสเตอร์ ถ้าจำได้อย่างเดียว สามารถติดตามองค์ประกอบอื่นได้ในลักษณะเดียวกัน

บทบาท
ความรับผิดชอบ
ตำแหน่งการนำไปใช้งาน
State
ค่าที่ไม่เปลี่ยนแปลงซึ่งแอปส่งผ่าน เช่น พิกัด สี zIndex ฯลฯ ส่งคืนแฮชของเนื้อหาด้วย fingerPrint()
core
Entity
ระเบียนหนึ่งรายการที่รวบรวม State วัตถุที่ผู้ให้บริการสร้างขึ้นจริง และ fingerPrint ในขณะนั้น
core
Manager
ทะเบียนที่เก็บ Entity ด้วย id การทดสอบการชนจากพิกัด (find) ก็ดูแลที่นี่
core
Controller
จุดเชื่อมต่อสำหรับการดำเนินการที่มี add / update / clear / find / onCameraChanged / destroy ดำเนินการตรวจสอบความแตกต่าง
core
Overlay
หน่วยการวาดภาพที่รวมองค์ประกอบชนิดเดียวกัน กำหนดลำดับการซ้อนทับโดย zIndex
core
OverlayRenderer
แปลง onAdd / onChange / onRemove / onPostProcess เป็นการเรียก SDK ของแผนที่แต่ละตัว เพียงแค่สืบทอดคลาสนามธรรมที่เตรียมไว้สำหรับแต่ละองค์ประกอบ (เช่น AbstractPolygonOverlayRenderer) และกรอกวิธีการทั้ง 3 วิธี
driver
Capable
ประกาศเป็นประเภทว่าผู้ให้บริการสามารถจัดการองค์ประกอบใดได้ (compositionPolygons / updatePolygon / hasPolygon) มีหนึ่งรายการสำหรับแต่ละองค์ประกอบ และตั้งชื่อเช่น PolygonCapableInterface ใน Kotlin และ PolygonCapable ใน TypeScript Swift ไม่ได้แสดงด้วยโปรโตคอล แต่ด้วยว่าผู้ให้บริการนั้นมี PolygonController หรือไม่
driver

สิ่งสำคัญคือตำแหน่งของขอบเขต ด้าน Core (State / Entity / Manager / Controller / Overlay) เขียนด้วยโค้ดเกือบเหมือนกันใน 3 ภาษา และสำหรับแต่ละผู้ให้บริการจำเป็นต้องเขียนเพียง Renderer และการประกาศว่าสามารถจัดการองค์ประกอบเหล่านั้นได้เท่านั้น

ANDROID · Kotlin
iOS · Swift
REACT · TypeScript
core/polygon/PolygonManager.kt
PolygonCapableInterface.kt
AbstractPolygonOverlayRenderer.kt
polygon/PolygonManager.swift
polygon/PolygonOverlayRenderer.swift
polygon/PolygonHoleSplit.swift
polygon/PolygonManager.ts
polygon/PolygonCapable.ts
AbstractPolygonOverlayRenderer.ts

05 · ขั้นตอนการนำความแตกต่างไปใช้

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

controller/OverlayRendererInterface.ts · สัญญาร่วมสำหรับ 3 แพลตฟอร์ม
export interface OverlayRendererInterface<ActualType, StateType, EntityType> {
    onAdd(data: StateType[]): Promise<Array<ActualType | null>> | Array<ActualType | null>;
    onChange(data: Array<ChangeParamsInterface<EntityType>>): Promise<Array<ActualType | null>> | Array<ActualType | null>;
    onRemove(data: EntityType[]): Promise<void> | void;
    onPostProcess(): Promise<void> | void;
}

onChange รับทั้ง Entity ก่อนหน้า (prev) และ State ปัจจุบัน (current) ไดรเวอร์สามารถอัปเดตเฉพาะคุณสมบัติที่เปลี่ยนแปลงได้ และลายเซ็นเหมือนกันใน Kotlin / Swift

06 · สิ่งที่ไดรเวอร์ต้องนำไปใช้

งานในการรองรับแผนที่ SDK ใหม่จำกัดอยู่ที่การสืบทอดจากเรนเดอร์นามธรรมสำหรับแต่ละองค์ประกอบและกรอกข้อมูลใน 3 การดำเนินการ ไม่ต้องแตะโมเดลของ Core หรือตรรกะความแตกต่าง

android-for-googlemaps · การนำเรนเดอร์ไปใช้
// ไดรเวอร์สืบทอดจากเรนเดอเรอร์นามธรรม แล้วเติมเพียงสามการทำงาน
internal class GoogleMapPolygonOverlayRenderer(
    override val holder: GoogleMapViewHolder,
    override val coroutine: CoroutineScope,
) : AbstractPolygonOverlayRenderer<GoogleMapActualPolygon>() {

    override suspend fun createPolygon(state: PolygonState) = /* map.addPolygon(...) */
    override suspend fun updatePolygonProperties(polygon, current, prev) = /* นำเฉพาะส่วนต่างไปใช้ */
    override suspend fun removePolygon(entity: PolygonEntityInterface<GoogleMapActualPolygon>) =
        entity.polygon.remove()
}

มีเพียง 3 การดำเนินการ คือ สร้าง อัปเดต และลบ เท่านั้นที่เฉพาะเจาะจงกับผู้ให้บริการ การตัดสินความแตกต่าง การจัดการทะเบียน และการทดสอบการโจมตีจะถูกเรียกหลังจาก Core ทำเสร็จแล้ว

07 · ขอบเขตของการนามธรรมและทางออกฉุกเฉิน

MapConductor ไม่ได้รวมฟังก์ชันทั้งหมดของแต่ละ SDK แผนที่ มุ่งเน้นการดำเนินการที่ใช้ร่วมกันบ่อย และสำหรับความต้องการที่เกินกว่านั้น จะมีทางออกสองทางที่เตรียมไว้

getMapViewHolder() · เรียกฟังก์ชันดั้งเดิมโดยตรง
// map ของอินเทอร์เฟซร่วมเป็น unknown ให้แคบลงเป็นชนิดของผู้ให้บริการก่อนใช้
const holder = mapViewState.getMapViewHolder();
const map = holder?.map as maplibregl.Map | undefined;

map?.addLayer({ id: 'buildings', type: 'fill-extrusion', source: 'composite' });

การออกแบบที่ทำให้ API ทั่วไปยังคงเรียบง่าย และในขณะเดียวกันก็ไม่สูญเสียข้อดีเฉพาะของผู้ให้บริการแต่ละราย สำหรับรายละเอียด โปรดดู "Native Extensions"

หน้าที่เกี่ยวข้อง