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