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