maplibre_native_ffi/
projection.rs1use std::fmt;
2
3use maplibre_native_ffi_core as maplibre_core;
4use maplibre_native_ffi_core::ptr::const_ptr_or_null;
5use maplibre_native_ffi_core::values::{empty_lat_lng, empty_screen_point, lat_lngs_to_native};
6use maplibre_native_ffi_sys as sys;
7
8use crate::camera::CameraOptionsNativeExt;
9use crate::handle::{ConcurrentNativeHandle, closed_handle_error, out_handle};
10use crate::values::NativeValue;
11use crate::{
12 CameraOptions, EdgeInsets, Error, HandleOperationError, LatLng, MapHandle, Result, ScreenPoint,
13};
14
15#[derive(Debug)]
16pub(crate) struct MapProjectionState {
17 handle: ConcurrentNativeHandle<sys::mln_map_projection>,
18}
19
20impl MapProjectionState {
21 fn new(native: sys::mln_map_projection) -> Result<Self> {
22 let handle = unsafe {
25 ConcurrentNativeHandle::from_handle(
26 native,
27 sys::mln_map_projection_destroy,
28 "mln_map_projection",
29 )
30 }?;
31 Ok(Self { handle })
32 }
33
34 fn native(&self) -> Result<sys::mln_map_projection> {
35 self.handle
36 .live_handle()
37 .ok_or_else(|| closed_handle_error("MapProjectionHandle"))
38 }
39
40 fn is_closed(&self) -> bool {
41 self.handle.is_closed()
42 }
43
44 fn close(&self) -> Result<()> {
45 self.handle.close()
46 }
47}
48
49pub struct MapProjectionHandle {
54 inner: MapProjectionState,
55}
56
57impl fmt::Debug for MapProjectionHandle {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 f.debug_struct("MapProjectionHandle")
60 .field("closed", &self.inner.is_closed())
61 .finish()
62 }
63}
64
65impl MapProjectionHandle {
66 pub(crate) fn new(map: &MapHandle) -> Result<Self> {
67 let map_ptr = map.inner.native()?;
68 let mut out = maplibre_core::ptr::OutHandle::<sys::mln_map_projection>::new();
69 maplibre_core::check(unsafe { sys::mln_map_projection_create(map_ptr, out.as_mut_ptr()) })?;
72 let ptr = out_handle(out, "mln_map_projection")?;
73 Ok(Self {
74 inner: MapProjectionState::new(ptr)?,
75 })
76 }
77
78 pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
80 self.inner
81 .close()
82 .map_err(|error| HandleOperationError::new(error, self))
83 }
84
85 pub fn camera(&self) -> Result<CameraOptions> {
87 let projection = self.inner.native()?;
88 let mut raw = unsafe { sys::mln_camera_options_default() };
90 maplibre_core::check(unsafe { sys::mln_map_projection_get_camera(projection, &mut raw) })?;
92 Ok(CameraOptions::from_native(raw))
93 }
94
95 pub fn set_camera(&self, camera: &CameraOptions) -> Result<()> {
97 let projection = self.inner.native()?;
98 let raw = camera.to_native();
99 maplibre_core::check(unsafe { sys::mln_map_projection_set_camera(projection, &raw) })
102 }
103
104 pub fn set_visible_coordinates(
106 &self,
107 coordinates: &[LatLng],
108 padding: EdgeInsets,
109 ) -> Result<()> {
110 let projection = self.inner.native()?;
111 if coordinates.is_empty() {
112 return Err(Error::invalid_argument(
113 "set_visible_coordinates requires at least one coordinate",
114 ));
115 }
116 let raw_coordinates = lat_lngs_to_native(coordinates);
117 maplibre_core::check(unsafe {
120 sys::mln_map_projection_set_visible_coordinates(
121 projection,
122 const_ptr_or_null(&raw_coordinates),
123 raw_coordinates.len(),
124 padding.to_native(),
125 )
126 })
127 }
128
129 pub fn set_visible_geometry(&self, geometry: &[u8], padding: EdgeInsets) -> Result<()> {
131 let projection = self.inner.native()?;
132 let native_geometry = maplibre_core::string::buffer_view(geometry);
133 maplibre_core::check(unsafe {
136 sys::mln_map_projection_set_visible_geometry(
137 projection,
138 native_geometry,
139 padding.to_native(),
140 )
141 })
142 }
143
144 pub fn pixel_for_lat_lng(&self, coordinate: LatLng) -> Result<ScreenPoint> {
146 let projection = self.inner.native()?;
147 let mut raw_point = empty_screen_point();
148 maplibre_core::check(unsafe {
151 sys::mln_map_projection_pixel_for_lat_lng(
152 projection,
153 coordinate.to_native(),
154 &mut raw_point,
155 )
156 })?;
157 Ok(ScreenPoint::from_native(raw_point))
158 }
159
160 pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
164 let projection = self.inner.native()?;
165 let mut raw_coordinate = empty_lat_lng();
166 maplibre_core::check(unsafe {
169 sys::mln_map_projection_lat_lng_for_pixel(
170 projection,
171 point.to_native(),
172 &mut raw_coordinate,
173 )
174 })?;
175 Ok(LatLng::from_native(raw_coordinate))
176 }
177
178 pub fn lat_lng_for_pixel_unwrapped(&self, point: ScreenPoint) -> Result<LatLng> {
183 let projection = self.inner.native()?;
184 let mut raw_coordinate = empty_lat_lng();
185 maplibre_core::check(unsafe {
188 sys::mln_map_projection_lat_lng_for_pixel_unwrapped(
189 projection,
190 point.to_native(),
191 &mut raw_coordinate,
192 )
193 })?;
194 Ok(LatLng::from_native(raw_coordinate))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use static_assertions::assert_impl_all;
201
202 use super::*;
203 use crate::{ErrorKind, MapOptions, RuntimeHandle};
204
205 assert_impl_all!(MapProjectionHandle: Send, Sync);
206
207 #[test]
208 fn projection_create_round_trip_close_and_stays_live_after_map_close() {
210 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
211 let map = MapHandle::with_options(&runtime, &MapOptions::new(512, 512, 1.0)).unwrap();
212 let center = LatLng::new(37.7749, -122.4194);
213 let mut camera_options = CameraOptions::default();
214 camera_options.center = Some(center);
215 camera_options.zoom = Some(5.0);
216 map.jump_to(&camera_options).unwrap();
217
218 let projection = map.create_projection().unwrap();
219 map.close().unwrap();
220 runtime.close().unwrap();
221
222 std::thread::spawn(move || {
223 let point = projection.pixel_for_lat_lng(center).unwrap();
224 let round_tripped = projection.lat_lng_for_pixel(point).unwrap();
225 assert!((round_tripped.latitude - center.latitude).abs() < 1e-7);
226 assert!((round_tripped.longitude - center.longitude).abs() < 1e-7);
227 projection.close().unwrap();
228 })
229 .join()
230 .unwrap();
231 }
232
233 #[test]
234 fn projection_drops_without_explicit_close() {
237 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
238 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
239
240 {
241 let _projection = map.create_projection().unwrap();
242 }
243
244 map.close().unwrap();
245 runtime.close().unwrap();
246 }
247
248 #[test]
249 fn projection_camera_and_visible_region_helpers_call_c_api() {
251 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
252 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
253 let projection = map.create_projection().unwrap();
254
255 let mut camera_options = CameraOptions::default();
256 camera_options.center = Some(LatLng::new(0.0, 0.0));
257 camera_options.zoom = Some(2.0);
258 projection.set_camera(&camera_options).unwrap();
259 let camera = projection.camera().unwrap();
260 assert_eq!(camera.center, Some(LatLng::new(0.0, 0.0)));
261 assert_eq!(camera.zoom, Some(2.0));
262
263 let padding = EdgeInsets::new(0.0, 0.0, 0.0, 0.0);
264 projection
265 .set_visible_coordinates(&[LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)], padding)
266 .unwrap();
267 let error = projection
268 .set_visible_coordinates(&[], padding)
269 .unwrap_err();
270 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
271 assert_eq!(error.raw_status(), None);
272 assert!(error.diagnostic().contains("at least one coordinate"));
273 projection
274 .set_visible_geometry(
275 br#"{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0]]}"#,
276 padding,
277 )
278 .unwrap();
279
280 projection.close().unwrap();
281 map.close().unwrap();
282 runtime.close().unwrap();
283 }
284}