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() {
193 let runtime = RuntimeHandle::new().unwrap();
194 let map = runtime
195 .create_map_with_options(&MapOptions::new(512, 512, 1.0))
196 .unwrap();
197 let center = LatLng::new(37.7749, -122.4194);
198 map.jump_to(&CameraOptions::new().with_center(center).with_zoom(5.0))
199 .unwrap();
200
201 let projection = map.create_projection().unwrap();
202 map.close().unwrap();
203 runtime.close().unwrap();
204
205 let point = projection.pixel_for_lat_lng(center).unwrap();
206 let round_tripped = projection.lat_lng_for_pixel(point).unwrap();
207 assert!((round_tripped.latitude - center.latitude).abs() < 1e-7);
208 assert!((round_tripped.longitude - center.longitude).abs() < 1e-7);
209
210 projection.close().unwrap();
211 }
212
213 #[test]
214 fn projection_drops_without_explicit_close() {
215 let runtime = RuntimeHandle::new().unwrap();
216 let map = runtime.create_map().unwrap();
217
218 {
219 let _projection = map.create_projection().unwrap();
220 }
221
222 map.close().unwrap();
223 runtime.close().unwrap();
224 }
225
226 #[test]
227 fn projection_camera_and_visible_region_helpers_call_c_api() {
228 let runtime = RuntimeHandle::new().unwrap();
229 let map = runtime.create_map().unwrap();
230 let projection = map.create_projection().unwrap();
231
232 projection
233 .set_camera(
234 &CameraOptions::new()
235 .with_center(LatLng::new(0.0, 0.0))
236 .with_zoom(2.0),
237 )
238 .unwrap();
239 let camera = projection.camera().unwrap();
240 assert_eq!(camera.center, Some(LatLng::new(0.0, 0.0)));
241 assert_eq!(camera.zoom, Some(2.0));
242
243 let padding = EdgeInsets::new(0.0, 0.0, 0.0, 0.0);
244 projection
245 .set_visible_coordinates(&[LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)], padding)
246 .unwrap();
247 let error = projection
248 .set_visible_coordinates(&[], padding)
249 .unwrap_err();
250 assert_eq!(error.kind(), ErrorKind::InvalidArgument);
251 assert_eq!(error.raw_status(), None);
252 assert!(error.diagnostic().contains("at least one coordinate"));
253 projection
254 .set_visible_geometry(
255 &Geometry::LineString(vec![LatLng::new(0.0, 0.0), LatLng::new(1.0, 1.0)]),
256 padding,
257 )
258 .unwrap();
259
260 projection.close().unwrap();
261 map.close().unwrap();
262 runtime.close().unwrap();
263 }
264}