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::{ThreadAffineNativeHandle, 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: ThreadAffineNativeHandle<sys::mln_map_projection>,
18}
19
20impl MapProjectionState {
21 fn new(native: sys::mln_map_projection) -> Result<Self> {
22 let handle = unsafe {
25 ThreadAffineNativeHandle::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> {
162 let projection = self.inner.native()?;
163 let mut raw_coordinate = empty_lat_lng();
164 maplibre_core::check(unsafe {
167 sys::mln_map_projection_lat_lng_for_pixel(
168 projection,
169 point.to_native(),
170 &mut raw_coordinate,
171 )
172 })?;
173 Ok(LatLng::from_native(raw_coordinate))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use static_assertions::assert_not_impl_any;
180
181 use super::*;
182 use crate::{ErrorKind, MapOptions, RuntimeHandle};
183
184 assert_not_impl_any!(MapProjectionHandle: Send, Sync);
185
186 #[test]
187 fn projection_create_round_trip_close_and_stays_live_after_map_close() {
189 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
190 let map = MapHandle::with_options(&runtime, &MapOptions::new(512, 512, 1.0)).unwrap();
191 let center = LatLng::new(37.7749, -122.4194);
192 let mut camera_options = CameraOptions::default();
193 camera_options.center = Some(center);
194 camera_options.zoom = Some(5.0);
195 map.jump_to(&camera_options).unwrap();
196
197 let projection = map.create_projection().unwrap();
198 map.close().unwrap();
199 runtime.close().unwrap();
200
201 let point = projection.pixel_for_lat_lng(center).unwrap();
202 let round_tripped = projection.lat_lng_for_pixel(point).unwrap();
203 assert!((round_tripped.latitude - center.latitude).abs() < 1e-7);
204 assert!((round_tripped.longitude - center.longitude).abs() < 1e-7);
205
206 projection.close().unwrap();
207 }
208
209 #[test]
210 fn projection_drops_without_explicit_close() {
213 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
214 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
215
216 {
217 let _projection = map.create_projection().unwrap();
218 }
219
220 map.close().unwrap();
221 runtime.close().unwrap();
222 }
223
224 #[test]
225 fn projection_camera_and_visible_region_helpers_call_c_api() {
227 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
228 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
229 let projection = map.create_projection().unwrap();
230
231 let mut camera_options = CameraOptions::default();
232 camera_options.center = Some(LatLng::new(0.0, 0.0));
233 camera_options.zoom = Some(2.0);
234 projection.set_camera(&camera_options).unwrap();
235 let camera = projection.camera().unwrap();
236 assert_eq!(camera.center, Some(LatLng::new(0.0, 0.0)));
237 assert_eq!(camera.zoom, Some(2.0));
238
239 let padding = EdgeInsets::new(0.0, 0.0, 0.0, 0.0);
240 projection
241 .set_visible_coordinates(&[LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)], padding)
242 .unwrap();
243 let error = projection
244 .set_visible_coordinates(&[], padding)
245 .unwrap_err();
246 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
247 assert_eq!(error.raw_status(), None);
248 assert!(error.diagnostic().contains("at least one coordinate"));
249 projection
250 .set_visible_geometry(
251 br#"{"type":"LineString","coordinates":[[0.0,0.0],[1.0,1.0]]}"#,
252 padding,
253 )
254 .unwrap();
255
256 projection.close().unwrap();
257 map.close().unwrap();
258 runtime.close().unwrap();
259 }
260}