maplibre_native/
projection.rs1use std::fmt;
2
3use maplibre_native_core as maplibre_core;
4use maplibre_native_core::ptr::const_ptr_or_null;
5use maplibre_native_core::values::{empty_lat_lng, empty_screen_point, lat_lngs_to_native};
6use maplibre_native_sys as sys;
7
8use crate::camera::CameraOptionsNativeExt;
9use crate::geometry::GeometryNativeExt;
10use crate::handle::{ThreadAffineNativeHandle, closed_handle_error, out_handle};
11use crate::values::NativeValue;
12use crate::{
13 CameraOptions, EdgeInsets, Error, Geometry, HandleOperationError, LatLng, MapHandle, Result,
14 ScreenPoint,
15};
16
17#[derive(Debug)]
18pub(crate) struct MapProjectionState {
19 handle: ThreadAffineNativeHandle<sys::mln_map_projection>,
20}
21
22impl MapProjectionState {
23 fn new(ptr: std::ptr::NonNull<sys::mln_map_projection>) -> Self {
24 let handle = unsafe {
27 ThreadAffineNativeHandle::from_raw(
28 ptr,
29 sys::mln_map_projection_destroy,
30 "mln_map_projection",
31 )
32 };
33 Self { handle }
34 }
35
36 fn as_ptr(&self) -> Result<*mut sys::mln_map_projection> {
37 let ptr = self.handle.as_ptr();
38 if ptr.is_null() {
39 Err(closed_handle_error("MapProjectionHandle"))
40 } else {
41 Ok(ptr)
42 }
43 }
44
45 fn is_closed(&self) -> bool {
46 self.handle.is_closed()
47 }
48
49 fn close(&self) -> Result<()> {
50 self.handle.close()
51 }
52}
53
54pub struct MapProjectionHandle {
59 inner: MapProjectionState,
60}
61
62impl fmt::Debug for MapProjectionHandle {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.debug_struct("MapProjectionHandle")
65 .field("closed", &self.inner.is_closed())
66 .finish()
67 }
68}
69
70impl MapProjectionHandle {
71 pub(crate) fn new(map: &MapHandle) -> Result<Self> {
72 let map_ptr = map.inner.as_ptr()?;
73 let mut out = maplibre_core::ptr::OutPtr::<sys::mln_map_projection>::new();
74 maplibre_core::check(unsafe { sys::mln_map_projection_create(map_ptr, out.as_mut_ptr()) })?;
77 let ptr = out_handle(out, "mln_map_projection")?;
78 Ok(Self {
79 inner: MapProjectionState::new(ptr),
80 })
81 }
82
83 pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
85 self.inner
86 .close()
87 .map_err(|error| HandleOperationError::new(error, self))
88 }
89
90 pub fn camera(&self) -> Result<CameraOptions> {
92 let projection = self.inner.as_ptr()?;
93 let mut raw = unsafe { sys::mln_camera_options_default() };
95 maplibre_core::check(unsafe { sys::mln_map_projection_get_camera(projection, &mut raw) })?;
97 Ok(CameraOptions::from_native(raw))
98 }
99
100 pub fn set_camera(&self, camera: &CameraOptions) -> Result<()> {
102 let projection = self.inner.as_ptr()?;
103 let raw = camera.to_native();
104 maplibre_core::check(unsafe { sys::mln_map_projection_set_camera(projection, &raw) })
107 }
108
109 pub fn set_visible_coordinates(
111 &self,
112 coordinates: &[LatLng],
113 padding: EdgeInsets,
114 ) -> Result<()> {
115 let projection = self.inner.as_ptr()?;
116 if coordinates.is_empty() {
117 return Err(Error::invalid_argument(
118 "set_visible_coordinates requires at least one coordinate",
119 ));
120 }
121 let raw_coordinates = lat_lngs_to_native(coordinates);
122 maplibre_core::check(unsafe {
125 sys::mln_map_projection_set_visible_coordinates(
126 projection,
127 const_ptr_or_null(&raw_coordinates),
128 raw_coordinates.len(),
129 padding.to_native(),
130 )
131 })
132 }
133
134 pub fn set_visible_geometry(&self, geometry: &Geometry, padding: EdgeInsets) -> Result<()> {
136 let projection = self.inner.as_ptr()?;
137 let native_geometry = geometry.try_to_native()?;
138 maplibre_core::check(unsafe {
141 sys::mln_map_projection_set_visible_geometry(
142 projection,
143 native_geometry.as_ptr(),
144 padding.to_native(),
145 )
146 })
147 }
148
149 pub fn pixel_for_lat_lng(&self, coordinate: LatLng) -> Result<ScreenPoint> {
151 let projection = self.inner.as_ptr()?;
152 let mut raw_point = empty_screen_point();
153 maplibre_core::check(unsafe {
156 sys::mln_map_projection_pixel_for_lat_lng(
157 projection,
158 coordinate.to_native(),
159 &mut raw_point,
160 )
161 })?;
162 Ok(ScreenPoint::from_native(raw_point))
163 }
164
165 pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
167 let projection = self.inner.as_ptr()?;
168 let mut raw_coordinate = empty_lat_lng();
169 maplibre_core::check(unsafe {
172 sys::mln_map_projection_lat_lng_for_pixel(
173 projection,
174 point.to_native(),
175 &mut raw_coordinate,
176 )
177 })?;
178 Ok(LatLng::from_native(raw_coordinate))
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use static_assertions::assert_not_impl_any;
185
186 use super::*;
187 use crate::{ErrorKind, MapOptions, RuntimeHandle};
188
189 assert_not_impl_any!(MapProjectionHandle: Send, Sync);
190
191 #[test]
192 fn projection_create_round_trip_close_and_stays_live_after_map_close() {
194 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
195 let map = MapHandle::with_options(&runtime, &MapOptions::new(512, 512, 1.0)).unwrap();
196 let center = LatLng::new(37.7749, -122.4194);
197 let mut camera_options = CameraOptions::default();
198 camera_options.center = Some(center);
199 camera_options.zoom = Some(5.0);
200 map.jump_to(&camera_options).unwrap();
201
202 let projection = map.create_projection().unwrap();
203 map.close().unwrap();
204 runtime.close().unwrap();
205
206 let point = projection.pixel_for_lat_lng(center).unwrap();
207 let round_tripped = projection.lat_lng_for_pixel(point).unwrap();
208 assert!((round_tripped.latitude - center.latitude).abs() < 1e-7);
209 assert!((round_tripped.longitude - center.longitude).abs() < 1e-7);
210
211 projection.close().unwrap();
212 }
213
214 #[test]
215 fn projection_drops_without_explicit_close() {
218 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
219 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
220
221 {
222 let _projection = map.create_projection().unwrap();
223 }
224
225 map.close().unwrap();
226 runtime.close().unwrap();
227 }
228
229 #[test]
230 fn projection_camera_and_visible_region_helpers_call_c_api() {
232 let runtime = RuntimeHandle::with_options(&crate::RuntimeOptions::default()).unwrap();
233 let map = MapHandle::with_options(&runtime, &MapOptions::default()).unwrap();
234 let projection = map.create_projection().unwrap();
235
236 let mut camera_options = CameraOptions::default();
237 camera_options.center = Some(LatLng::new(0.0, 0.0));
238 camera_options.zoom = Some(2.0);
239 projection.set_camera(&camera_options).unwrap();
240 let camera = projection.camera().unwrap();
241 assert_eq!(camera.center, Some(LatLng::new(0.0, 0.0)));
242 assert_eq!(camera.zoom, Some(2.0));
243
244 let padding = EdgeInsets::new(0.0, 0.0, 0.0, 0.0);
245 projection
246 .set_visible_coordinates(&[LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)], padding)
247 .unwrap();
248 let error = projection
249 .set_visible_coordinates(&[], padding)
250 .unwrap_err();
251 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
252 assert_eq!(error.raw_status(), None);
253 assert!(error.diagnostic().contains("at least one coordinate"));
254 projection
255 .set_visible_geometry(
256 &Geometry::LineString(vec![LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)]),
257 padding,
258 )
259 .unwrap();
260
261 projection.close().unwrap();
262 map.close().unwrap();
263 runtime.close().unwrap();
264 }
265}