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 Self::from_native(ptr)
74 }
75
76 pub(crate) fn from_native(ptr: sys::mln_map_projection) -> Result<Self> {
77 Ok(Self {
78 inner: MapProjectionState::new(ptr)?,
79 })
80 }
81
82 pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
84 self.inner
85 .close()
86 .map_err(|error| HandleOperationError::new(error, self))
87 }
88
89 pub fn camera(&self) -> Result<CameraOptions> {
91 let projection = self.inner.native()?;
92 let mut raw = unsafe { sys::mln_camera_options_default() };
94 maplibre_core::check(unsafe { sys::mln_map_projection_get_camera(projection, &mut raw) })?;
96 Ok(CameraOptions::from_native(raw))
97 }
98
99 pub fn set_camera(&self, camera: &CameraOptions) -> Result<()> {
101 let projection = self.inner.native()?;
102 let raw = camera.to_native();
103 maplibre_core::check(unsafe { sys::mln_map_projection_set_camera(projection, &raw) })
106 }
107
108 pub fn set_visible_coordinates(
110 &self,
111 coordinates: &[LatLng],
112 padding: EdgeInsets,
113 ) -> Result<()> {
114 let projection = self.inner.native()?;
115 if coordinates.is_empty() {
116 return Err(Error::invalid_argument(
117 "set_visible_coordinates requires at least one coordinate",
118 ));
119 }
120 let raw_coordinates = lat_lngs_to_native(coordinates);
121 maplibre_core::check(unsafe {
124 sys::mln_map_projection_set_visible_coordinates(
125 projection,
126 const_ptr_or_null(&raw_coordinates),
127 raw_coordinates.len(),
128 padding.to_native(),
129 )
130 })
131 }
132
133 pub fn set_visible_geometry(&self, geometry: &[u8], padding: EdgeInsets) -> Result<()> {
135 let projection = self.inner.native()?;
136 let native_geometry = maplibre_core::string::buffer_view(geometry);
137 maplibre_core::check(unsafe {
140 sys::mln_map_projection_set_visible_geometry(
141 projection,
142 native_geometry,
143 padding.to_native(),
144 )
145 })
146 }
147
148 pub fn pixel_for_lat_lng(&self, coordinate: LatLng) -> Result<ScreenPoint> {
150 let projection = self.inner.native()?;
151 let mut raw_point = empty_screen_point();
152 maplibre_core::check(unsafe {
155 sys::mln_map_projection_pixel_for_lat_lng(
156 projection,
157 coordinate.to_native(),
158 &mut raw_point,
159 )
160 })?;
161 Ok(ScreenPoint::from_native(raw_point))
162 }
163
164 pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
168 let projection = self.inner.native()?;
169 let mut raw_coordinate = empty_lat_lng();
170 maplibre_core::check(unsafe {
173 sys::mln_map_projection_lat_lng_for_pixel(
174 projection,
175 point.to_native(),
176 &mut raw_coordinate,
177 )
178 })?;
179 Ok(LatLng::from_native(raw_coordinate))
180 }
181
182 pub fn lat_lng_for_pixel_unwrapped(&self, point: ScreenPoint) -> Result<LatLng> {
187 let projection = self.inner.native()?;
188 let mut raw_coordinate = empty_lat_lng();
189 maplibre_core::check(unsafe {
192 sys::mln_map_projection_lat_lng_for_pixel_unwrapped(
193 projection,
194 point.to_native(),
195 &mut raw_coordinate,
196 )
197 })?;
198 Ok(LatLng::from_native(raw_coordinate))
199 }
200
201 pub fn meters_per_pixel_at_latitude(&self, latitude: f64) -> Result<f64> {
204 let projection = self.inner.native()?;
205 let mut meters_per_pixel = 0.0;
206 maplibre_core::check(unsafe {
209 sys::mln_map_projection_meters_per_pixel_at_latitude(
210 projection,
211 latitude,
212 &mut meters_per_pixel,
213 )
214 })?;
215 Ok(meters_per_pixel)
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use static_assertions::assert_impl_all;
222
223 use super::*;
224 use crate::{ErrorKind, MapOptions, RuntimeHandle};
225
226 assert_impl_all!(MapProjectionHandle: Send, Sync);
227
228 #[test]
229 fn projection_create_round_trip_close_and_stays_live_after_map_close() {
231 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
232 let map = MapHandle::with_options(&runtime, &MapOptions::new(512, 512, 1.0)).unwrap();
233 let center = LatLng::new(37.7749, -122.4194);
234 let mut camera_options = CameraOptions::default();
235 camera_options.center = Some(center);
236 camera_options.zoom = Some(5.0);
237 map.jump_to(&camera_options).unwrap();
238
239 let projection = map.create_projection().unwrap();
240 map.close().unwrap();
241 runtime.close().unwrap();
242
243 std::thread::spawn(move || {
244 let point = projection.pixel_for_lat_lng(center).unwrap();
245 let round_tripped = projection.lat_lng_for_pixel(point).unwrap();
246 assert!((round_tripped.latitude - center.latitude).abs() < 1e-7);
247 assert!((round_tripped.longitude - center.longitude).abs() < 1e-7);
248 projection.close().unwrap();
249 })
250 .join()
251 .unwrap();
252 }
253
254 #[test]
255 fn projection_drops_without_explicit_close() {
258 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
259 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
260
261 {
262 let _projection = map.create_projection().unwrap();
263 }
264
265 map.close().unwrap();
266 runtime.close().unwrap();
267 }
268
269 #[test]
270 fn projection_camera_and_visible_region_helpers_call_c_api() {
272 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
273 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
274 let projection = map.create_projection().unwrap();
275
276 let mut camera_options = CameraOptions::default();
277 camera_options.center = Some(LatLng::new(0.0, 0.0));
278 camera_options.zoom = Some(2.0);
279 projection.set_camera(&camera_options).unwrap();
280 let camera = projection.camera().unwrap();
281 assert_eq!(camera.center, Some(LatLng::new(0.0, 0.0)));
282 assert_eq!(camera.zoom, Some(2.0));
283
284 let padding = EdgeInsets::new(0.0, 0.0, 0.0, 0.0);
285 projection
286 .set_visible_coordinates(&[LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)], padding)
287 .unwrap();
288 let error = projection
289 .set_visible_coordinates(&[], padding)
290 .unwrap_err();
291 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
292 assert_eq!(error.raw_status(), None);
293 assert!(error.diagnostic().contains("at least one coordinate"));
294 projection
295 .set_visible_geometry(
296 br#"{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0]]}"#,
297 padding,
298 )
299 .unwrap();
300
301 projection.close().unwrap();
302 map.close().unwrap();
303 runtime.close().unwrap();
304 }
305}