เอกสาร / มุมมองแผนที่ / อ่านตำแหน่งกล้อง

อ่านตำแหน่งกล้อง

ในตัวควบคุมของ MapConductor ไม่มี getCameraPosition() เหมือนกันทั้ง 3 แพลตฟอร์ม ตั้งแต่เป็นเมธอดแรกที่มักค้นหาเมื่อใช้งานแผนที่ ผมจึงจะระบุไว้ที่นี่ว่าควรใช้อะไรแทน และทำไมจึงไม่จัดเตรียมไว้

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

01 · 3 เส้นทาง

ใช้ตามวัตถุประสงค์ ทั้งหมดจะได้รับ MapCameraPosition เดียวกัน และช่วงที่มองเห็นอยู่ใน visibleRegion

ต้องการค่าปัจจุบัน
mapViewState.cameraPosition — อ่านได้ตลอดเวลา ช่วงที่มองเห็นคือ cameraPosition.visibleRegion.bounds
ต้องการติดตามการเปลี่ยนแปลง
onCameraMove / onCameraMoveEnd — เริ่มทำงานแม้ระหว่างการเคลื่อนที่ ถ้าต้องการให้ UI ตามไปด้วย ให้ใช้ตัวนี้
โมดูลส่วนขยาย
ลงทะเบียนเป็นตัวควบคุมโอเวอร์เลย์แล้วรับ onCameraChanged ทำให้ไม่ต้องแย่งชิง listener ช่องเดียวของตัวควบคุมแผนที่
แพลตฟอร์ม
อ่านจากสถานะ
val camera = mapViewState.cameraPosition
val bounds = camera.visibleRegion?.bounds

// ติดตามการเปลี่ยนแปลง
MapView(
    state = mapViewState,
    onCameraMoveEnd = { camera -> reload(camera.visibleRegion?.bounds) },
)

02 · ทำไมไม่มีเมธอด get

MapConductor บนทั้ง 3 แพลตฟอร์มอยู่บน UI แบบประกาศ (Compose / SwiftUI / React) ใน UI แบบประกาศ สถานะจะมีตำแหน่งเดียวที่เก็บ "สภาพปัจจุบัน" และ UI แค่สะท้อนมันเท่านั้น กล้องก็เป็นสถานะเช่นกัน ดังนั้นจึงวางไว้ใน state

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

การไหลแบบพุช (push)
เหตุการณ์กล้องจาก SDK แผนที่
        ↓  ผู้ให้บริการอ่านค่าดิบของ SDK แปลงเป็นซูมมาตรฐาน แล้วแนบ visibleRegion
   state.updateCameraPosition(camera)
        ↓
   ├─ mapViewState.cameraPosition                (อ่านได้ตลอดเวลา)
   ├─ onCameraMove / onCameraMoveEnd             (คอลแบ็กของแอป)
   └─ onCameraChanged ของโอเวอร์เลย์ที่ลงทะเบียน  (โมดูลส่วนขยาย)
รูปแบบนี้เหมือนกันใน android-sdk / ios-sdk / react-sdk ทั้งสองแบบเนทีฟตั้งแต่แรกไม่มี API การรับค่าในคอนโทรลเลอร์ มีเพียง react-sdk เท่านั้นที่มี ดังนั้นเราจึงนำออกเมื่อวันที่ 2026-08-06

03 · ต้นทุนของการดึง (pull)

หากพูดถึงแค่เรื่องการออกแบบ มักจะกลายเป็นว่า “เพิ่มอีกตัวนึงก็ได้” ดังนั้นผมจึงจะวางค่าที่วัดได้จริงไว้ การประกอบกล้องหนึ่งครั้ง ไม่ได้ถูกเลย

สิ่งที่เกิดขึ้นในการเรียกครั้งเดียว

อ่าน center / zoom / bearing / tilt จาก SDK → แปลงเป็นระดับซูมแบบรวมด้วย ZoomAltitudeConverter สำหรับผู้ให้บริการแต่ละราย → ทำการย้อนภาพมุมทั้งสี่ของหน้าจอแล้วประกอบ visibleRegion ขั้นตอนสุดท้ายหนักที่สุด และไม่ใช่ getter ธรรมดา

ค่าที่วัดได้จริง ผมเปิดหน้าเมarker ของ MapLibre ใน Chromium และนับจำนวนครั้งที่ getCameraPosition() ถูกเรียกในการลากครั้งเดียว (การแพนประมาณ 20 เฟรม)

การโหลดครั้งแรก
การลากครั้งเดียว
ในช่วงที่มีเมธอดการรับค่า
5
86
หลังจากนำออก
3
30

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

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

การนำไปใช้จริงในวิดีโอ · Android + MapLibreKotlin · Jetpack Compose
var camera by remember { mutableStateOf<MapCameraPosition?>(null) }

MapLibreMapView(
    state = mapViewState,
    modifier = Modifier.weight(1f),
    // Fires throughout the gesture — keep this handler cheap
    onCameraMove = { position -> camera = position },
    // Once, when the map settles. Fetching and redrawing belong here
    onCameraMoveEnd = { position -> viewModel.onCameraSettled(position) },
)

camera?.let { position ->
    Text("lat  %.5f".format(position.position.latitude))
    Text("lng  %.5f".format(position.position.longitude))
    Text("zoom %.2f".format(position.zoom))
    // Read the visible region off the value you were handed; do not rebuild it
    position.visibleRegion?.bounds?.let { bounds ->
        Text("SW ${bounds.southWest}  NE ${bounds.northEast}")
    }
}
วิดีโอตัวอย่าง · อ่านค่าขณะขยับแผนที่
ยังไม่ได้ถ่ายวิดีโอระหว่างการลาก ละติจูด ลองจิจูด และซูมที่ได้รับผ่านคอลแบ็กจะถูกอัปเดตบนหน้าจออย่างต่อเนื่อง ความแตกต่างระหว่าง pull และ push จะแสดงออกที่วิธีการติดตามนี้

04 · ความล่าช้าหนึ่งเฟรม

mapViewState.cameraPosition คือ “ค่าล่าสุดที่ถูกพุช” ดังนั้นในทางทฤษฎีมันอาจจะเก่ากว่าค่าปัจจุบันของ SDK หนึ่งเฟรม นี่คือเหตุผลเชิงสาระสำคัญเพียงอย่างเดียวที่จะเพิ่มเมธอดการรับค่า ดังนั้นผมจึงเขียนไว้อย่างตรงไปตรงมา

ในทางปฏิบัติ

onCameraMove ถูกยิงในทุกเฟรมขณะเคลื่อนที่ ดังนั้น state จึงทันตามเฟรมก่อนหน้าเสมอ ไม่มีความแตกต่างที่รับรู้ได้ทั้งในการแสดงผล การรับข้อมูล และการตรวจสอบช่วง จริงๆ แล้ว หลังจากเอาเมธอดการรับออก การทดสอบบราวเซอร์ทั้ง 41 รายการยังผ่านเหมือนเดิม

ถ้าจะมีผล

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

05 · เมื่อยังต้องการค่าดิบอยู่ดี

หากไม่เพียงพอผ่าน statе — เช่น ต้องการให้ซิงค์กับการวนลูปการวาดของแผนที่ SDK — ให้ดึงอินสแตนซ์แผนที่เนทีฟออกมาผ่าน MapViewHolder โดยไม่ต้องไปทางอ้อมผ่าน API แบบรวม จะเป็นโค้ดเฉพาะของผู้ให้บริการ แต่ก็จะเร็วและแม่นยำที่สุด

ลงไประดับเนทีฟ
const holder = mapViewState.getMapViewHolder();
const map = holder?.map as maplibregl.Map | undefined;
const liveZoom = map?.getZoom();
ภายในผู้ให้บริการแต่ละรายมีกระบวนการแปลงค่าดิบของ SDK เป็นซูมแบบรวม และค่าสำหรับเหตุการณ์กล้องจะถูกสร้างขึ้นที่นั่น อย่างไรก็ตาม นั่นเป็นภายในของการใช้งานและไม่ได้เปิดออกมาในส่วนติดต่อสาธารณะ ทั้งสามแพลตฟอร์มใช้หลักการเดียวกัน (android-sdk ใช้ getMapCameraPosition() แบบ private, ios-sdk ใช้ currentCameraPosition(from:) แบบ private)

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