Skip to main content

maplibre_native/
map.rs

1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::fmt;
4use std::rc::Rc;
5
6use maplibre_native_core as maplibre_core;
7use maplibre_native_core::ptr::{const_ptr_or_null, mut_ptr_or_null, option_ptr};
8use maplibre_native_core::values::{
9    empty_lat_lng, empty_lat_lng_bounds as empty_bounds, empty_screen_point, lat_lngs_to_native,
10    screen_points_to_native,
11};
12use maplibre_native_sys as sys;
13
14use crate::camera::{
15    AnimationOptionsNativeExt, BoundOptionsNativeExt, CameraFitOptionsNativeExt,
16    CameraOptionsNativeExt, FreeCameraOptionsNativeExt, ProjectionModeNativeExt,
17};
18#[cfg(test)]
19use crate::custom_geometry::CanonicalTileId;
20use crate::custom_geometry::CustomGeometrySourceState;
21use crate::events::MapId;
22use crate::geometry::GeometryNativeExt;
23use crate::handle::{ThreadAffineNativeHandle, closed_handle_error, out_handle};
24use crate::options::{MapOptionsNativeExt, MapTileOptionsNativeExt, MapViewportOptionsNativeExt};
25use crate::render::{
26    MetalBorrowedTextureDescriptor, MetalOwnedTextureDescriptor, MetalSurfaceDescriptor,
27    OpenGLBorrowedTextureDescriptor, OpenGLOwnedTextureDescriptor, OpenGLSurfaceDescriptor,
28    RenderSessionHandle, VulkanBorrowedTextureDescriptor, VulkanOwnedTextureDescriptor,
29    VulkanSurfaceDescriptor,
30};
31use crate::runtime::{RuntimeHandle, RuntimeState};
32use crate::values::NativeValue;
33use crate::{
34    AnimationOptions, BoundOptions, CameraFitOptions, CameraOptions, Error, ErrorKind,
35    FreeCameraOptions, Geometry, HandleOperationError, LatLng, LatLngBounds, MapDebugOptions,
36    MapOptions, MapProjectionHandle, MapTileOptions, MapViewportOptions, ProjectionMode, Result,
37    ScreenPoint,
38};
39#[cfg(test)]
40use crate::{GeoJson, JsonValue, PremultipliedRgba8Image};
41
42mod style;
43pub use style::{
44    LocationIndicatorImageKind, RasterDemEncoding, SourceInfo, SourceType, StyleImage,
45    StyleImageInfo, StyleImageOptions, TileScheme, TileSourceOptions, VectorTileEncoding,
46};
47
48#[derive(Debug)]
49pub(crate) struct MapState {
50    handle: ThreadAffineNativeHandle<sys::mln_map>,
51    runtime: RefCell<Option<Rc<RuntimeState>>>,
52    id: MapId,
53    custom_geometry_sources: RefCell<HashMap<String, Box<CustomGeometrySourceState>>>,
54}
55
56impl MapState {
57    fn new(ptr: std::ptr::NonNull<sys::mln_map>, runtime: Rc<RuntimeState>, id: MapId) -> Self {
58        // SAFETY: ptr came from successful mln_map_create and is paired with
59        // the matching map destroy function.
60        let handle =
61            unsafe { ThreadAffineNativeHandle::from_raw(ptr, sys::mln_map_destroy, "mln_map") };
62        Self {
63            handle,
64            runtime: RefCell::new(Some(runtime)),
65            id,
66            custom_geometry_sources: RefCell::new(HashMap::new()),
67        }
68    }
69
70    pub(crate) fn as_ptr(&self) -> Result<*mut sys::mln_map> {
71        let ptr = self.handle.as_ptr();
72        if ptr.is_null() {
73            Err(closed_handle_error("MapHandle"))
74        } else {
75            Ok(ptr)
76        }
77    }
78
79    fn is_closed(&self) -> bool {
80        self.handle.is_closed()
81    }
82
83    fn close(&self) -> Result<()> {
84        let ptr = self.handle.as_ptr();
85        self.handle.close()?;
86        if let Some(runtime) = self.runtime.borrow_mut().take() {
87            runtime.unregister_map(ptr);
88        }
89        self.clear_custom_geometry_sources();
90        Ok(())
91    }
92
93    pub(crate) fn clear_custom_geometry_sources(&self) {
94        self.custom_geometry_sources.borrow_mut().clear();
95    }
96
97    pub(crate) fn release_detached_custom_geometry_sources(&self) {
98        let map = match self.as_ptr() {
99            Ok(map) => map,
100            Err(_) => return,
101        };
102        let source_ids = self
103            .custom_geometry_sources
104            .borrow()
105            .keys()
106            .cloned()
107            .collect::<Vec<_>>();
108        let mut detached = Vec::new();
109        for source_id in source_ids {
110            let source_id_view = maplibre_core::string::string_view(&source_id);
111            let mut source_type = 0;
112            let mut found = false;
113            // SAFETY: map is live, source_id_view is valid for this call, and
114            // output pointers refer to writable storage.
115            let status = unsafe {
116                sys::mln_map_get_style_source_type(
117                    map,
118                    source_id_view.raw(),
119                    &mut source_type,
120                    &mut found,
121                )
122            };
123            if status == sys::MLN_STATUS_OK
124                && (!found || source_type != sys::MLN_STYLE_SOURCE_TYPE_CUSTOM_VECTOR)
125            {
126                detached.push(source_id);
127            }
128        }
129        if !detached.is_empty() {
130            let mut sources = self.custom_geometry_sources.borrow_mut();
131            for source_id in detached {
132                sources.remove(&source_id);
133            }
134        }
135    }
136}
137
138impl Drop for MapState {
139    fn drop(&mut self) {
140        if let Some(runtime) = self.runtime.borrow_mut().take() {
141            runtime.unregister_map(self.handle.as_ptr());
142        }
143    }
144}
145
146/// Owner-thread map handle bound to a retained runtime.
147pub struct MapHandle {
148    pub(crate) inner: Rc<MapState>,
149}
150
151impl fmt::Debug for MapHandle {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        f.debug_struct("MapHandle")
154            .field("closed", &self.inner.is_closed())
155            .finish()
156    }
157}
158
159impl MapHandle {
160    /// Creates a map with native default map options on the runtime owner thread.
161    pub fn new(runtime: &RuntimeHandle) -> Result<Self> {
162        Self::with_options(runtime, &MapOptions::default())
163    }
164
165    /// Creates a map with explicit map options on the runtime owner thread.
166    pub fn with_options(runtime: &RuntimeHandle, options: &MapOptions) -> Result<Self> {
167        let runtime_ptr = runtime.inner.as_ptr()?;
168        let mut out = maplibre_core::ptr::OutPtr::<sys::mln_map>::new();
169        let raw_options = options.to_native();
170
171        // SAFETY: runtime_ptr is a live runtime handle. raw_options is a
172        // materialized map descriptor with size filled by the binding. out is a
173        // valid null-initialized out-pointer owned by this call.
174        maplibre_core::check(unsafe {
175            sys::mln_map_create(runtime_ptr, &raw_options, out.as_mut_ptr())
176        })?;
177        let ptr = out_handle(out, "mln_map")?;
178        let id = runtime.inner.register_map(ptr.as_ptr());
179        let state = Rc::new(MapState::new(ptr, Rc::clone(&runtime.inner), id));
180        runtime
181            .inner
182            .register_map_state(ptr.as_ptr(), Rc::downgrade(&state));
183
184        Ok(Self { inner: state })
185    }
186
187    /// Returns this map's runtime-local event source identity.
188    pub fn id(&self) -> MapId {
189        self.inner.id
190    }
191
192    #[cfg(test)]
193    fn custom_geometry_source_count_for_testing(&self) -> usize {
194        self.inner.custom_geometry_sources.borrow().len()
195    }
196
197    /// Explicitly destroys the map.
198    ///
199    /// Native destruction errors are returned. When destruction fails, the
200    /// underlying native handle remains live in the shared state so future child
201    /// handles can continue to retain and close the map safely.
202    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
203        if self.inner.is_closed() {
204            return Ok(());
205        }
206        if Rc::strong_count(&self.inner) > 1 {
207            return Err(HandleOperationError::new(
208                Error::new(
209                    ErrorKind::InvalidState,
210                    None,
211                    "MapHandle cannot close while child handles are live",
212                ),
213                self,
214            ));
215        }
216        self.inner
217            .close()
218            .map_err(|error| HandleOperationError::new(error, self))
219    }
220
221    /// Requests a repaint for a continuous map.
222    pub fn request_repaint(&self) -> Result<()> {
223        let map = self.inner.as_ptr()?;
224        // SAFETY: map is a live map handle owned by this wrapper.
225        maplibre_core::check(unsafe { sys::mln_map_request_repaint(map) })
226    }
227
228    /// Requests one still image for a static or tile map.
229    pub fn request_still_image(&self) -> Result<()> {
230        let map = self.inner.as_ptr()?;
231        // SAFETY: map is a live map handle owned by this wrapper.
232        maplibre_core::check(unsafe { sys::mln_map_request_still_image(map) })
233    }
234
235    /// Applies MapLibre debug overlay mask bits.
236    pub fn set_debug_options(&self, options: MapDebugOptions) -> Result<()> {
237        let map = self.inner.as_ptr()?;
238        // SAFETY: map is live. The C API validates unknown mask bits.
239        maplibre_core::check(unsafe { sys::mln_map_set_debug_options(map, options.bits()) })
240    }
241
242    /// Reads MapLibre debug overlay mask bits.
243    pub fn debug_options(&self) -> Result<MapDebugOptions> {
244        let map = self.inner.as_ptr()?;
245        let mut raw = 0;
246        // SAFETY: map is live and out_options points to writable u32 storage.
247        maplibre_core::check(unsafe { sys::mln_map_get_debug_options(map, &mut raw) })?;
248        Ok(MapDebugOptions::from_bits_retain(raw))
249    }
250
251    /// Enables or disables MapLibre's rendering stats overlay view.
252    pub fn set_rendering_stats_view_enabled(&self, enabled: bool) -> Result<()> {
253        let map = self.inner.as_ptr()?;
254        // SAFETY: map is live and enabled is passed by value.
255        maplibre_core::check(unsafe { sys::mln_map_set_rendering_stats_view_enabled(map, enabled) })
256    }
257
258    /// Reads whether MapLibre's rendering stats overlay view is enabled.
259    pub fn rendering_stats_view_enabled(&self) -> Result<bool> {
260        let map = self.inner.as_ptr()?;
261        let mut enabled = false;
262        // SAFETY: map is live and out_enabled points to writable bool storage.
263        maplibre_core::check(unsafe {
264            sys::mln_map_get_rendering_stats_view_enabled(map, &mut enabled)
265        })?;
266        Ok(enabled)
267    }
268
269    /// Reads whether MapLibre currently considers the map fully loaded.
270    pub fn is_fully_loaded(&self) -> Result<bool> {
271        let map = self.inner.as_ptr()?;
272        let mut loaded = false;
273        // SAFETY: map is live and out_loaded points to writable bool storage.
274        maplibre_core::check(unsafe { sys::mln_map_is_fully_loaded(map, &mut loaded) })?;
275        Ok(loaded)
276    }
277
278    /// Dumps map debug logs through MapLibre Native logging.
279    pub fn dump_debug_logs(&self) -> Result<()> {
280        let map = self.inner.as_ptr()?;
281        // SAFETY: map is live.
282        maplibre_core::check(unsafe { sys::mln_map_dump_debug_logs(map) })
283    }
284
285    /// Reads live viewport and render-transform controls.
286    pub fn viewport_options(&self) -> Result<MapViewportOptions> {
287        let map = self.inner.as_ptr()?;
288        // SAFETY: Default constructor takes no arguments and initializes size.
289        let mut raw = unsafe { sys::mln_map_viewport_options_default() };
290        // SAFETY: map is live and raw has a valid size field for C to fill.
291        maplibre_core::check(unsafe { sys::mln_map_get_viewport_options(map, &mut raw) })?;
292        Ok(MapViewportOptions::from_native(raw))
293    }
294
295    /// Applies selected live viewport and render-transform controls.
296    pub fn set_viewport_options(&self, options: &MapViewportOptions) -> Result<()> {
297        let map = self.inner.as_ptr()?;
298        let raw = options.to_native();
299        // SAFETY: map is live and raw is a materialized descriptor valid for
300        // the duration of this call.
301        maplibre_core::check(unsafe { sys::mln_map_set_viewport_options(map, &raw) })
302    }
303
304    /// Reads tile prefetch and LOD tuning controls.
305    pub fn tile_options(&self) -> Result<MapTileOptions> {
306        let map = self.inner.as_ptr()?;
307        // SAFETY: Default constructor takes no arguments and initializes size.
308        let mut raw = unsafe { sys::mln_map_tile_options_default() };
309        // SAFETY: map is live and raw has a valid size field for C to fill.
310        maplibre_core::check(unsafe { sys::mln_map_get_tile_options(map, &mut raw) })?;
311        Ok(MapTileOptions::from_native(raw))
312    }
313
314    /// Applies selected tile prefetch and LOD tuning controls.
315    pub fn set_tile_options(&self, options: &MapTileOptions) -> Result<()> {
316        let map = self.inner.as_ptr()?;
317        let raw = options.to_native();
318        // SAFETY: map is live and raw is a materialized descriptor valid for
319        // the duration of this call.
320        maplibre_core::check(unsafe { sys::mln_map_set_tile_options(map, &raw) })
321    }
322
323    /// Reads the current camera snapshot.
324    pub fn camera(&self) -> Result<CameraOptions> {
325        let map = self.inner.as_ptr()?;
326        // SAFETY: Default constructor takes no arguments and initializes size.
327        let mut raw = unsafe { sys::mln_camera_options_default() };
328        // SAFETY: map is live and raw has a valid size field for C to fill.
329        maplibre_core::check(unsafe { sys::mln_map_get_camera(map, &mut raw) })?;
330        Ok(CameraOptions::from_native(raw))
331    }
332
333    /// Applies a camera jump command.
334    pub fn jump_to(&self, camera: &CameraOptions) -> Result<()> {
335        let map = self.inner.as_ptr()?;
336        let raw = camera.to_native();
337        // SAFETY: map is live and raw is a materialized descriptor valid for
338        // the duration of this call.
339        maplibre_core::check(unsafe { sys::mln_map_jump_to(map, &raw) })
340    }
341
342    /// Applies a camera ease transition command.
343    pub fn ease_to(
344        &self,
345        camera: &CameraOptions,
346        animation: Option<&AnimationOptions>,
347    ) -> Result<()> {
348        let map = self.inner.as_ptr()?;
349        let raw_camera = camera.to_native();
350        let raw_animation = animation.map(AnimationOptions::to_native);
351        // SAFETY: map is live and descriptors are valid for this call. A null
352        // animation pointer requests native defaults.
353        maplibre_core::check(unsafe {
354            sys::mln_map_ease_to(map, &raw_camera, option_ptr(raw_animation.as_ref()))
355        })
356    }
357
358    /// Applies a camera fly transition command.
359    pub fn fly_to(
360        &self,
361        camera: &CameraOptions,
362        animation: Option<&AnimationOptions>,
363    ) -> Result<()> {
364        let map = self.inner.as_ptr()?;
365        let raw_camera = camera.to_native();
366        let raw_animation = animation.map(AnimationOptions::to_native);
367        // SAFETY: map is live and descriptors are valid for this call. A null
368        // animation pointer requests native defaults.
369        maplibre_core::check(unsafe {
370            sys::mln_map_fly_to(map, &raw_camera, option_ptr(raw_animation.as_ref()))
371        })
372    }
373
374    /// Applies a screen-space pan command.
375    pub fn move_by(&self, delta_x: f64, delta_y: f64) -> Result<()> {
376        let map = self.inner.as_ptr()?;
377        // SAFETY: map is live. The C API validates numeric values.
378        maplibre_core::check(unsafe { sys::mln_map_move_by(map, delta_x, delta_y) })
379    }
380
381    /// Applies an animated screen-space pan command.
382    pub fn move_by_animated(
383        &self,
384        delta_x: f64,
385        delta_y: f64,
386        animation: Option<&AnimationOptions>,
387    ) -> Result<()> {
388        let map = self.inner.as_ptr()?;
389        let raw_animation = animation.map(AnimationOptions::to_native);
390        // SAFETY: map is live and the optional animation descriptor is valid
391        // for this call. The C API validates numeric values.
392        maplibre_core::check(unsafe {
393            sys::mln_map_move_by_animated(map, delta_x, delta_y, option_ptr(raw_animation.as_ref()))
394        })
395    }
396
397    /// Applies a screen-space zoom command.
398    pub fn scale_by(&self, scale: f64, anchor: Option<ScreenPoint>) -> Result<()> {
399        let map = self.inner.as_ptr()?;
400        let raw_anchor = anchor.map(ScreenPoint::to_native);
401        // SAFETY: map is live and the optional anchor pointer is valid for this
402        // call. The C API validates numeric values.
403        maplibre_core::check(unsafe {
404            sys::mln_map_scale_by(map, scale, option_ptr(raw_anchor.as_ref()))
405        })
406    }
407
408    /// Applies an animated screen-space zoom command.
409    pub fn scale_by_animated(
410        &self,
411        scale: f64,
412        anchor: Option<ScreenPoint>,
413        animation: Option<&AnimationOptions>,
414    ) -> Result<()> {
415        let map = self.inner.as_ptr()?;
416        let raw_anchor = anchor.map(ScreenPoint::to_native);
417        let raw_animation = animation.map(AnimationOptions::to_native);
418        // SAFETY: map is live and optional descriptors are valid for this call.
419        // The C API validates numeric values.
420        maplibre_core::check(unsafe {
421            sys::mln_map_scale_by_animated(
422                map,
423                scale,
424                option_ptr(raw_anchor.as_ref()),
425                option_ptr(raw_animation.as_ref()),
426            )
427        })
428    }
429
430    /// Applies a screen-space rotate command.
431    pub fn rotate_by(&self, first: ScreenPoint, second: ScreenPoint) -> Result<()> {
432        let map = self.inner.as_ptr()?;
433        // SAFETY: map is live. Points are passed by value and validated by C.
434        maplibre_core::check(unsafe {
435            sys::mln_map_rotate_by(map, first.to_native(), second.to_native())
436        })
437    }
438
439    /// Applies an animated screen-space rotate command.
440    pub fn rotate_by_animated(
441        &self,
442        first: ScreenPoint,
443        second: ScreenPoint,
444        animation: Option<&AnimationOptions>,
445    ) -> Result<()> {
446        let map = self.inner.as_ptr()?;
447        let raw_animation = animation.map(AnimationOptions::to_native);
448        // SAFETY: map is live and optional animation descriptor is valid for
449        // this call. Points are passed by value and validated by C.
450        maplibre_core::check(unsafe {
451            sys::mln_map_rotate_by_animated(
452                map,
453                first.to_native(),
454                second.to_native(),
455                option_ptr(raw_animation.as_ref()),
456            )
457        })
458    }
459
460    /// Applies a pitch delta command.
461    pub fn pitch_by(&self, pitch: f64) -> Result<()> {
462        let map = self.inner.as_ptr()?;
463        // SAFETY: map is live. The C API validates numeric values.
464        maplibre_core::check(unsafe { sys::mln_map_pitch_by(map, pitch) })
465    }
466
467    /// Applies an animated pitch delta command.
468    pub fn pitch_by_animated(
469        &self,
470        pitch: f64,
471        animation: Option<&AnimationOptions>,
472    ) -> Result<()> {
473        let map = self.inner.as_ptr()?;
474        let raw_animation = animation.map(AnimationOptions::to_native);
475        // SAFETY: map is live and optional animation descriptor is valid for
476        // this call. The C API validates numeric values.
477        maplibre_core::check(unsafe {
478            sys::mln_map_pitch_by_animated(map, pitch, option_ptr(raw_animation.as_ref()))
479        })
480    }
481
482    /// Cancels active camera transitions.
483    pub fn cancel_transitions(&self) -> Result<()> {
484        let map = self.inner.as_ptr()?;
485        // SAFETY: map is live.
486        maplibre_core::check(unsafe { sys::mln_map_cancel_transitions(map) })
487    }
488
489    /// Computes a camera that fits geographic bounds in the current viewport.
490    pub fn camera_for_lat_lng_bounds(
491        &self,
492        bounds: LatLngBounds,
493        fit_options: Option<&CameraFitOptions>,
494    ) -> Result<CameraOptions> {
495        let map = self.inner.as_ptr()?;
496        let raw_fit = fit_options.map(CameraFitOptions::to_native);
497        // SAFETY: Default constructor takes no arguments and initializes size.
498        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
499        // SAFETY: map is live, bounds is passed by value, optional fit options
500        // are valid for this call, and raw_camera is writable.
501        maplibre_core::check(unsafe {
502            sys::mln_map_camera_for_lat_lng_bounds(
503                map,
504                bounds.to_native(),
505                option_ptr(raw_fit.as_ref()),
506                &mut raw_camera,
507            )
508        })?;
509        Ok(CameraOptions::from_native(raw_camera))
510    }
511
512    /// Computes a camera that fits geographic coordinates in the current viewport.
513    pub fn camera_for_lat_lngs(
514        &self,
515        coordinates: &[LatLng],
516        fit_options: Option<&CameraFitOptions>,
517    ) -> Result<CameraOptions> {
518        let map = self.inner.as_ptr()?;
519        if coordinates.is_empty() {
520            return Err(Error::invalid_argument(
521                "camera_for_lat_lngs requires at least one coordinate",
522            ));
523        }
524        let raw_coordinates = lat_lngs_to_native(coordinates);
525        let raw_fit = fit_options.map(CameraFitOptions::to_native);
526        // SAFETY: Default constructor takes no arguments and initializes size.
527        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
528        // SAFETY: map is live, arrays are valid for coordinate_count non-empty
529        // entries, optional fit options are valid, and raw_camera is writable.
530        maplibre_core::check(unsafe {
531            sys::mln_map_camera_for_lat_lngs(
532                map,
533                const_ptr_or_null(&raw_coordinates),
534                raw_coordinates.len(),
535                option_ptr(raw_fit.as_ref()),
536                &mut raw_camera,
537            )
538        })?;
539        Ok(CameraOptions::from_native(raw_camera))
540    }
541
542    /// Computes a camera that fits a geometry in the current viewport.
543    pub fn camera_for_geometry(
544        &self,
545        geometry: &Geometry,
546        fit_options: Option<&CameraFitOptions>,
547    ) -> Result<CameraOptions> {
548        let map = self.inner.as_ptr()?;
549        let native_geometry = geometry.try_to_native()?;
550        let raw_fit = fit_options.map(CameraFitOptions::to_native);
551        // SAFETY: Default constructor takes no arguments and initializes size.
552        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
553        // SAFETY: map is live, native_geometry owns backing storage for the
554        // duration of this call, optional fit options are valid, and raw_camera
555        // is writable.
556        maplibre_core::check(unsafe {
557            sys::mln_map_camera_for_geometry(
558                map,
559                native_geometry.as_ptr(),
560                option_ptr(raw_fit.as_ref()),
561                &mut raw_camera,
562            )
563        })?;
564        Ok(CameraOptions::from_native(raw_camera))
565    }
566
567    /// Computes wrapped geographic bounds for a camera in the current viewport.
568    pub fn lat_lng_bounds_for_camera(&self, camera: &CameraOptions) -> Result<LatLngBounds> {
569        let map = self.inner.as_ptr()?;
570        let raw_camera = camera.to_native();
571        let mut raw_bounds = empty_bounds();
572        // SAFETY: map is live, raw_camera is a valid descriptor for this call,
573        // and raw_bounds points to writable storage.
574        maplibre_core::check(unsafe {
575            sys::mln_map_lat_lng_bounds_for_camera(map, &raw_camera, &mut raw_bounds)
576        })?;
577        Ok(LatLngBounds::from_native(raw_bounds))
578    }
579
580    /// Computes unwrapped geographic bounds for a camera in the current viewport.
581    pub fn lat_lng_bounds_for_camera_unwrapped(
582        &self,
583        camera: &CameraOptions,
584    ) -> Result<LatLngBounds> {
585        let map = self.inner.as_ptr()?;
586        let raw_camera = camera.to_native();
587        let mut raw_bounds = empty_bounds();
588        // SAFETY: map is live, raw_camera is a valid descriptor for this call,
589        // and raw_bounds points to writable storage.
590        maplibre_core::check(unsafe {
591            sys::mln_map_lat_lng_bounds_for_camera_unwrapped(map, &raw_camera, &mut raw_bounds)
592        })?;
593        Ok(LatLngBounds::from_native(raw_bounds))
594    }
595
596    /// Reads map camera constraint options.
597    pub fn bounds(&self) -> Result<BoundOptions> {
598        let map = self.inner.as_ptr()?;
599        // SAFETY: Default constructor takes no arguments and initializes size.
600        let mut raw = unsafe { sys::mln_bound_options_default() };
601        // SAFETY: map is live and raw has a valid size field for C to fill.
602        maplibre_core::check(unsafe { sys::mln_map_get_bounds(map, &mut raw) })?;
603        Ok(BoundOptions::from_native(raw))
604    }
605
606    /// Applies selected map camera constraint options.
607    pub fn set_bounds(&self, options: &BoundOptions) -> Result<()> {
608        let map = self.inner.as_ptr()?;
609        let raw = options.to_native();
610        // SAFETY: map is live and raw is a valid descriptor for this call.
611        maplibre_core::check(unsafe { sys::mln_map_set_bounds(map, &raw) })
612    }
613
614    /// Reads the current free camera position and orientation.
615    pub fn free_camera_options(&self) -> Result<FreeCameraOptions> {
616        let map = self.inner.as_ptr()?;
617        // SAFETY: Default constructor takes no arguments and initializes size.
618        let mut raw = unsafe { sys::mln_free_camera_options_default() };
619        // SAFETY: map is live and raw has a valid size field for C to fill.
620        maplibre_core::check(unsafe { sys::mln_map_get_free_camera_options(map, &mut raw) })?;
621        Ok(FreeCameraOptions::from_native(raw))
622    }
623
624    /// Applies selected free camera position and orientation fields.
625    pub fn set_free_camera_options(&self, options: &FreeCameraOptions) -> Result<()> {
626        let map = self.inner.as_ptr()?;
627        let raw = options.to_native();
628        // SAFETY: map is live and raw is a valid descriptor for this call.
629        maplibre_core::check(unsafe { sys::mln_map_set_free_camera_options(map, &raw) })
630    }
631
632    /// Reads current axonometric rendering options.
633    pub fn projection_mode(&self) -> Result<ProjectionMode> {
634        let map = self.inner.as_ptr()?;
635        // SAFETY: Default constructor takes no arguments and initializes size.
636        let mut raw = unsafe { sys::mln_projection_mode_default() };
637        // SAFETY: map is live and raw has a valid size field for C to fill.
638        maplibre_core::check(unsafe { sys::mln_map_get_projection_mode(map, &mut raw) })?;
639        Ok(ProjectionMode::from_native(raw))
640    }
641
642    /// Applies selected axonometric rendering option fields.
643    pub fn set_projection_mode(&self, mode: &ProjectionMode) -> Result<()> {
644        let map = self.inner.as_ptr()?;
645        let raw = mode.to_native();
646        // SAFETY: map is live and raw is a valid descriptor for this call.
647        maplibre_core::check(unsafe { sys::mln_map_set_projection_mode(map, &raw) })
648    }
649
650    /// Converts a geographic world coordinate to a screen point for the current map.
651    pub fn pixel_for_lat_lng(&self, coordinate: LatLng) -> Result<ScreenPoint> {
652        let map = self.inner.as_ptr()?;
653        let mut raw_point = empty_screen_point();
654        // SAFETY: map is live, coordinate is passed by value, and raw_point is
655        // writable storage for the output.
656        maplibre_core::check(unsafe {
657            sys::mln_map_pixel_for_lat_lng(map, coordinate.to_native(), &mut raw_point)
658        })?;
659        Ok(ScreenPoint::from_native(raw_point))
660    }
661
662    /// Converts a screen point to a geographic world coordinate for the current map.
663    pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
664        let map = self.inner.as_ptr()?;
665        let mut raw_coordinate = empty_lat_lng();
666        // SAFETY: map is live, point is passed by value, and raw_coordinate is
667        // writable storage for the output.
668        maplibre_core::check(unsafe {
669            sys::mln_map_lat_lng_for_pixel(map, point.to_native(), &mut raw_coordinate)
670        })?;
671        Ok(LatLng::from_native(raw_coordinate))
672    }
673
674    /// Converts geographic world coordinates to screen points for the current map.
675    pub fn pixels_for_lat_lngs(&self, coordinates: &[LatLng]) -> Result<Vec<ScreenPoint>> {
676        let map = self.inner.as_ptr()?;
677        let raw_coordinates = lat_lngs_to_native(coordinates);
678        let mut raw_points = vec![empty_screen_point(); coordinates.len()];
679        // SAFETY: map is live. Input and output arrays are valid for len
680        // entries, or null when len is 0.
681        maplibre_core::check(unsafe {
682            sys::mln_map_pixels_for_lat_lngs(
683                map,
684                const_ptr_or_null(&raw_coordinates),
685                raw_coordinates.len(),
686                mut_ptr_or_null(&mut raw_points),
687            )
688        })?;
689        Ok(raw_points
690            .into_iter()
691            .map(ScreenPoint::from_native)
692            .collect())
693    }
694
695    /// Converts screen points to geographic world coordinates for the current map.
696    pub fn lat_lngs_for_pixels(&self, points: &[ScreenPoint]) -> Result<Vec<LatLng>> {
697        let map = self.inner.as_ptr()?;
698        let raw_points = screen_points_to_native(points);
699        let mut raw_coordinates = vec![empty_lat_lng(); points.len()];
700        // SAFETY: map is live. Input and output arrays are valid for len
701        // entries, or null when len is 0.
702        maplibre_core::check(unsafe {
703            sys::mln_map_lat_lngs_for_pixels(
704                map,
705                const_ptr_or_null(&raw_points),
706                raw_points.len(),
707                mut_ptr_or_null(&mut raw_coordinates),
708            )
709        })?;
710        Ok(raw_coordinates
711            .into_iter()
712            .map(LatLng::from_native)
713            .collect())
714    }
715
716    /// Creates a standalone projection snapshot from the current map transform.
717    pub fn create_projection(&self) -> Result<MapProjectionHandle> {
718        MapProjectionHandle::new(self)
719    }
720
721    /// Attaches a Metal native surface render target to this map.
722    ///
723    /// The layer and optional device pointers are backend-native handles. They
724    /// must name valid Metal objects for this session and remain usable on the
725    /// owner thread until the session is detached or closed.
726    pub fn attach_metal_surface(
727        &self,
728        descriptor: &MetalSurfaceDescriptor,
729    ) -> Result<RenderSessionHandle> {
730        let raw = descriptor.to_native();
731        RenderSessionHandle::attach(self, |map, out| {
732            // SAFETY: map is live, raw is a materialized descriptor valid for
733            // this call, and out is a null-initialized out-pointer.
734            unsafe { sys::mln_metal_surface_attach(map, &raw, out) }
735        })
736    }
737
738    /// Attaches a Vulkan native surface render target to this map.
739    ///
740    /// Vulkan handles are borrowed. They must remain valid and externally
741    /// synchronized until the session is detached or closed.
742    pub fn attach_vulkan_surface(
743        &self,
744        descriptor: &VulkanSurfaceDescriptor,
745    ) -> Result<RenderSessionHandle> {
746        let raw = descriptor.to_native();
747        RenderSessionHandle::attach(self, |map, out| {
748            // SAFETY: map is live, raw is a materialized descriptor valid for
749            // this call, and out is a null-initialized out-pointer.
750            unsafe { sys::mln_vulkan_surface_attach(map, &raw, out) }
751        })
752    }
753
754    /// Attaches an OpenGL native surface render target to this map.
755    ///
756    /// OpenGL context provider and surface handles are borrowed. They must
757    /// remain valid and externally synchronized until the session is detached
758    /// or closed.
759    pub fn attach_opengl_surface(
760        &self,
761        descriptor: &OpenGLSurfaceDescriptor,
762    ) -> Result<RenderSessionHandle> {
763        let raw = descriptor.to_native();
764        RenderSessionHandle::attach(self, |map, out| {
765            // SAFETY: map is live, raw is a materialized descriptor valid for
766            // this call, and out is a null-initialized out-pointer.
767            unsafe { sys::mln_opengl_surface_attach(map, &raw, out) }
768        })
769    }
770
771    /// Attaches a Metal session-owned texture render target to this map.
772    ///
773    /// The device pointer must name a valid Metal device that remains usable on
774    /// the owner thread until the session is detached or closed.
775    pub fn attach_metal_owned_texture(
776        &self,
777        descriptor: &MetalOwnedTextureDescriptor,
778    ) -> Result<RenderSessionHandle> {
779        let raw = descriptor.to_native();
780        RenderSessionHandle::attach(self, |map, out| {
781            // SAFETY: map is live, raw is a materialized descriptor valid for
782            // this call, and out is a null-initialized out-pointer.
783            unsafe { sys::mln_metal_owned_texture_attach(map, &raw, out) }
784        })
785    }
786
787    /// Attaches a Metal caller-owned texture render target to this map.
788    ///
789    /// The texture pointer is borrowed. The caller owns the texture, keeps it
790    /// valid until detach or close, and synchronizes use outside this session.
791    pub fn attach_metal_borrowed_texture(
792        &self,
793        descriptor: &MetalBorrowedTextureDescriptor,
794    ) -> Result<RenderSessionHandle> {
795        let raw = descriptor.to_native();
796        RenderSessionHandle::attach(self, |map, out| {
797            // SAFETY: map is live, raw is a materialized descriptor valid for
798            // this call, and out is a null-initialized out-pointer.
799            unsafe { sys::mln_metal_borrowed_texture_attach(map, &raw, out) }
800        })
801    }
802
803    /// Attaches a Vulkan session-owned texture render target to this map.
804    ///
805    /// Vulkan device and queue handles are borrowed. They must remain valid and
806    /// externally synchronized until the session is detached or closed.
807    pub fn attach_vulkan_owned_texture(
808        &self,
809        descriptor: &VulkanOwnedTextureDescriptor,
810    ) -> Result<RenderSessionHandle> {
811        let raw = descriptor.to_native();
812        RenderSessionHandle::attach(self, |map, out| {
813            // SAFETY: map is live, raw is a materialized descriptor valid for
814            // this call, and out is a null-initialized out-pointer.
815            unsafe { sys::mln_vulkan_owned_texture_attach(map, &raw, out) }
816        })
817    }
818
819    /// Attaches a Vulkan caller-owned texture render target to this map.
820    ///
821    /// Vulkan handles, image, and image view are borrowed. The caller owns the
822    /// image resources, keeps them valid until detach or close, and handles
823    /// queue-family ownership and synchronization outside this session.
824    pub fn attach_vulkan_borrowed_texture(
825        &self,
826        descriptor: &VulkanBorrowedTextureDescriptor,
827    ) -> Result<RenderSessionHandle> {
828        let raw = descriptor.to_native();
829        RenderSessionHandle::attach(self, |map, out| {
830            // SAFETY: map is live, raw is a materialized descriptor valid for
831            // this call, and out is a null-initialized out-pointer.
832            unsafe { sys::mln_vulkan_borrowed_texture_attach(map, &raw, out) }
833        })
834    }
835
836    /// Attaches an OpenGL session-owned texture render target to this map.
837    ///
838    /// The context provider handles are borrowed. They must remain valid until
839    /// the session is detached or closed. Host sampling must use a context in
840    /// the same share group while the acquired frame remains open.
841    pub fn attach_opengl_owned_texture(
842        &self,
843        descriptor: &OpenGLOwnedTextureDescriptor,
844    ) -> Result<RenderSessionHandle> {
845        let raw = descriptor.to_native();
846        RenderSessionHandle::attach(self, |map, out| {
847            // SAFETY: map is live, raw is a materialized descriptor valid for
848            // this call, and out is a null-initialized out-pointer.
849            unsafe { sys::mln_opengl_owned_texture_attach(map, &raw, out) }
850        })
851    }
852
853    /// Attaches an OpenGL caller-owned texture render target to this map.
854    ///
855    /// The context provider handles and texture object are borrowed. The caller
856    /// owns the texture, keeps it valid until detach or close, and synchronizes
857    /// use outside this session.
858    pub fn attach_opengl_borrowed_texture(
859        &self,
860        descriptor: &OpenGLBorrowedTextureDescriptor,
861    ) -> Result<RenderSessionHandle> {
862        let raw = descriptor.to_native();
863        RenderSessionHandle::attach(self, |map, out| {
864            // SAFETY: map is live, raw is a materialized descriptor valid for
865            // this call, and out is a null-initialized out-pointer.
866            unsafe { sys::mln_opengl_borrowed_texture_attach(map, &raw, out) }
867        })
868    }
869}
870
871#[cfg(test)]
872mod tests;