อ่านตำแหน่งกล้อง
ในตัวควบคุมของ MapConductor ไม่มี getCameraPosition() เหมือนกันทั้ง 3 แพลตฟอร์ม ตั้งแต่เป็นเมธอดแรกที่มักค้นหาเมื่อใช้งานแผนที่ ผมจึงจะระบุไว้ที่นี่ว่าควรใช้อะไรแทน และทำไมจึงไม่จัดเตรียมไว้
สรุปสั้นๆ กล้องคือสิ่งที่ เราไม่ได้ไปดึงมัน แต่มันถูกส่งมาหา ด้าน SDK ของแผนที่รู้ถึงการเปลี่ยนแปลงของกล้องอยู่แล้ว เราแค่รับมาใช้งานก็พอ
01 · 3 เส้นทาง
ใช้ตามวัตถุประสงค์ ทั้งหมดจะได้รับ MapCameraPosition เดียวกัน และช่วงที่มองเห็นอยู่ใน visibleRegion
mapViewState.cameraPosition — อ่านได้ตลอดเวลา ช่วงที่มองเห็นคือ cameraPosition.visibleRegion.boundsonCameraMove / onCameraMoveEnd — เริ่มทำงานแม้ระหว่างการเคลื่อนที่ ถ้าต้องการให้ UI ตามไปด้วย ให้ใช้ตัวนี้onCameraChanged ทำให้ไม่ต้องแย่งชิง listener ช่องเดียวของตัวควบคุมแผนที่val camera = mapViewState.cameraPosition
val bounds = camera.visibleRegion?.bounds
// ติดตามการเปลี่ยนแปลง
MapView(
state = mapViewState,
onCameraMoveEnd = { camera -> reload(camera.visibleRegion?.bounds) },
)let camera = mapViewState.cameraPosition
let bounds = camera.visibleRegion?.bounds
// ติดตามการเปลี่ยนแปลง
MapLibreMapView(state: mapViewState, onCameraMoveEnd: { camera in
reload(camera.visibleRegion?.bounds)
})const camera = mapViewState.cameraPosition;
const bounds = camera.visibleRegion?.bounds;
// ติดตามการเปลี่ยนแปลง
<MapLibreMapView
state={mapViewState}
onCameraMoveEnd={(camera) => reload(camera.visibleRegion?.bounds)}
/>02 · ทำไมไม่มีเมธอด get
MapConductor บนทั้ง 3 แพลตฟอร์มอยู่บน UI แบบประกาศ (Compose / SwiftUI / React) ใน UI แบบประกาศ สถานะจะมีตำแหน่งเดียวที่เก็บ "สภาพปัจจุบัน" และ UI แค่สะท้อนมันเท่านั้น กล้องก็เป็นสถานะเช่นกัน ดังนั้นจึงวางไว้ใน state
การเพิ่มเมธอดการรับค่าให้กับคอนโทรลเลอร์จะทำให้ค่าเดียวกันมีแหล่งที่มาสองที่ อย่างหนึ่งคือ state และอีกอย่างคือการอ่านโดยตรงจาก SDK ทั้งสองอย่างนี้ควรจะเหมือนกัน แต่กลับเพิ่มโอกาสที่จะไม่ตรงกัน ในความเป็นจริงเป็นเช่นนั้น ก่อนที่จะลบออก เรากำลังประกอบกล้องสองครั้งในเฟรมเดียวกัน — ครั้งหนึ่งสำหรับอีเวนต์ของ SDK และอีกครั้งคือการสร้างใหม่ทุกครั้งที่วาดใหม่
เหตุการณ์กล้องจาก SDK แผนที่
↓ ผู้ให้บริการอ่านค่าดิบของ SDK แปลงเป็นซูมมาตรฐาน แล้วแนบ visibleRegion
state.updateCameraPosition(camera)
↓
├─ mapViewState.cameraPosition (อ่านได้ตลอดเวลา)
├─ onCameraMove / onCameraMoveEnd (คอลแบ็กของแอป)
└─ onCameraChanged ของโอเวอร์เลย์ที่ลงทะเบียน (โมดูลส่วนขยาย)03 · ต้นทุนของการดึง (pull)
หากพูดถึงแค่เรื่องการออกแบบ มักจะกลายเป็นว่า “เพิ่มอีกตัวนึงก็ได้” ดังนั้นผมจึงจะวางค่าที่วัดได้จริงไว้ การประกอบกล้องหนึ่งครั้ง ไม่ได้ถูกเลย
สิ่งที่เกิดขึ้นในการเรียกครั้งเดียว
อ่าน center / zoom / bearing / tilt จาก SDK → แปลงเป็นระดับซูมแบบรวมด้วย ZoomAltitudeConverter สำหรับผู้ให้บริการแต่ละราย → ทำการย้อนภาพมุมทั้งสี่ของหน้าจอแล้วประกอบ visibleRegion ขั้นตอนสุดท้ายหนักที่สุด และไม่ใช่ getter ธรรมดา
ค่าที่วัดได้จริง ผมเปิดหน้าเมarker ของ MapLibre ใน Chromium และนับจำนวนครั้งที่ getCameraPosition() ถูกเรียกในการลากครั้งเดียว (การแพนประมาณ 20 เฟรม)
ลดลงประมาณ 65% ส่วนที่ลดลงไปนั้น เป็นการสร้างใหม่ทั้งหมด แม้ว่าจะประกอบหนึ่งครั้งในอีเวนต์ของ SDK และส่งไปยัง state แต่ในการวาดใหม่ทุกครั้งมุมมองก็ยังคงประกอบค่าเดิมเหมือนเดิมอีกครั้งหนึ่ง ผู้เรียกอยู่ในมุมมองทั้ง 13 แห่ง ซึ่งหมายความว่าทั้งหมดนั้นกำลังทิ้งค่าที่เพิ่งพุชไปเมื่อสักครู่นี้
โดยวิธีในขณะหยุดนิ่งคือ 0 ครั้ง มันไม่ได้เร่งออกไปเลย แต่เป็นประเภทของต้นทุนที่ มีผลอย่างแน่นอนเฉพาะขณะที่กำลังใช้งานเท่านั้น เนื่องจากถูกซ้อนทับในช่วงเวลาที่แผนที่หนักที่สุด จึงเป็นส่วนที่รับรู้ได้ง่าย
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}")
}
}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();