Skip to main content

maplibre_native_ffi/
map.rs

1use std::cell::RefCell;
2use std::fmt;
3use std::rc::Rc;
4
5use maplibre_native_ffi_core as maplibre_core;
6use maplibre_native_ffi_core::ptr::{const_ptr_or_null, mut_ptr_or_null, option_ptr};
7use maplibre_native_ffi_core::values::{
8    empty_lat_lng, empty_lat_lng_bounds as empty_bounds, empty_screen_point, lat_lngs_to_native,
9    screen_points_to_native,
10};
11use maplibre_native_ffi_sys as sys;
12
13#[cfg(test)]
14use crate::PremultipliedRgba8Image;
15use crate::camera::{
16    AnimationOptionsNativeExt, BoundOptionsNativeExt, CameraFitOptionsNativeExt,
17    CameraOptionsNativeExt, FreeCameraOptionsNativeExt, ProjectionModeNativeExt,
18};
19#[cfg(test)]
20use crate::custom_geometry::CanonicalTileId;
21use crate::events::MapId;
22use crate::handle::{ThreadAffineNativeHandle, closed_handle_error};
23use crate::options::{MapOptionsNativeExt, MapTileOptionsNativeExt, MapViewportOptionsNativeExt};
24use crate::render::{
25    MetalBorrowedTextureDescriptor, MetalOwnedTextureDescriptor, MetalSurfaceDescriptor,
26    OpenGLBorrowedTextureDescriptor, OpenGLOwnedTextureDescriptor, OpenGLSurfaceDescriptor,
27    RenderSessionHandle, VulkanBorrowedTextureDescriptor, VulkanOwnedTextureDescriptor,
28    VulkanSurfaceDescriptor, WebGpuBorrowedTextureDescriptor, WebGpuOwnedTextureDescriptor,
29    WebGpuSurfaceDescriptor,
30};
31use crate::runtime::{RuntimeHandle, RuntimeState};
32use crate::values::NativeValue;
33use crate::{
34    AnimationOptions, BoundOptions, CameraFitOptions, CameraOptions, Error, ErrorKind,
35    FreeCameraOptions, HandleOperationError, LatLng, LatLngBounds, MapDebugOptions, MapOptions,
36    MapProjectionHandle, MapTileOptions, MapViewportOptions, ProjectionMode, Result,
37    RuntimeEventMask, ScreenPoint,
38};
39
40mod style;
41pub use style::{
42    GeoJsonSourceOptions, ImageContent, ImageStretch, LocationIndicatorImageKind,
43    RasterDemEncoding, SourceInfo, SourceType, StyleImage, StyleImageInfo, StyleImageOptions,
44    StyleImageTextFit, StyleLayerVisibility, StyleTransitionOptions, TileJsonInfo, TileScheme,
45    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}
54
55impl MapState {
56    fn new(native: sys::mln_map, runtime: Rc<RuntimeState>, id: MapId) -> Result<Self> {
57        // SAFETY: native came from successful mln_map_create and is paired with
58        // the matching map destroy function.
59        let handle = unsafe {
60            ThreadAffineNativeHandle::from_handle(native, sys::mln_map_destroy, "mln_map")
61        }?;
62        Ok(Self {
63            handle,
64            runtime: RefCell::new(Some(runtime)),
65            id,
66        })
67    }
68
69    pub(crate) fn native(&self) -> Result<sys::mln_map> {
70        self.handle
71            .live_handle()
72            .ok_or_else(|| closed_handle_error("MapHandle"))
73    }
74
75    fn is_closed(&self) -> bool {
76        self.handle.is_closed()
77    }
78
79    fn close(&self) -> Result<()> {
80        // The destroy releases the callback state of this map's custom geometry
81        // sources before it returns.
82        self.handle.close()?;
83        self.runtime.borrow_mut().take();
84        Ok(())
85    }
86}
87
88impl Drop for MapState {
89    fn drop(&mut self) {
90        self.runtime.borrow_mut().take();
91        // A failed destroy, such as one with a render session still attached,
92        // is reported through the leak channel by the handle's own `Drop`.
93        let _ = self.handle.close();
94    }
95}
96
97/// Owner-thread map handle bound to a retained runtime.
98pub struct MapHandle {
99    pub(crate) inner: Rc<MapState>,
100}
101
102impl fmt::Debug for MapHandle {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.debug_struct("MapHandle")
105            .field("closed", &self.inner.is_closed())
106            .finish()
107    }
108}
109
110impl MapHandle {
111    /// Creates a map with explicit map options on the runtime owner thread.
112    pub fn with_options(runtime: &RuntimeHandle, options: &MapOptions) -> Result<Self> {
113        let runtime_ptr = runtime.inner.native()?;
114        let mut out = maplibre_core::ptr::OutHandle::<sys::mln_map>::new();
115        let raw_options = options.to_native()?;
116
117        // SAFETY: runtime_ptr is a live runtime handle. raw_options is a
118        // materialized map descriptor with size filled by the binding. out is a
119        // valid null-initialized out-pointer owned by this call.
120        maplibre_core::check(unsafe {
121            sys::mln_map_create(runtime_ptr, &raw_options, out.as_mut_ptr())
122        })?;
123        let native = out.get();
124        let id = MapId::new(native.0);
125        let state = Rc::new(MapState::new(native, Rc::clone(&runtime.inner), id)?);
126
127        Ok(Self { inner: state })
128    }
129
130    /// Returns this map's runtime-local event source identity.
131    pub fn id(&self) -> MapId {
132        self.inner.id
133    }
134
135    /// Explicitly destroys the map. A failed destroy leaves the native handle
136    /// live so child handles can keep retaining and closing the map.
137    ///
138    /// Closing discards this map's queued runtime events and its recorded
139    /// loading failure, with no flush and no terminal event. Dropping the
140    /// handle ends the event stream the same way.
141    pub fn close(self) -> std::result::Result<(), HandleOperationError<Self>> {
142        if self.inner.is_closed() {
143            return Ok(());
144        }
145        if Rc::strong_count(&self.inner) > 1 {
146            return Err(HandleOperationError::new(
147                Error::new(
148                    ErrorKind::InvalidState,
149                    None,
150                    "MapHandle cannot close while child handles are live",
151                ),
152                self,
153            ));
154        }
155        self.inner
156            .close()
157            .map_err(|error| HandleOperationError::new(error, self))
158    }
159
160    /// Requests a repaint for a continuous map.
161    pub fn request_repaint(&self) -> Result<()> {
162        let map = self.inner.native()?;
163        // SAFETY: map is a live map handle owned by this wrapper.
164        maplibre_core::check(unsafe { sys::mln_map_request_repaint(map) })
165    }
166
167    /// Requests one still image for a static or tile map.
168    pub fn request_still_image(&self) -> Result<()> {
169        let map = self.inner.native()?;
170        // SAFETY: map is a live map handle owned by this wrapper.
171        maplibre_core::check(unsafe { sys::mln_map_request_still_image(map) })
172    }
173
174    /// Selects which map-originated event types this map queues.
175    ///
176    /// A map reads the bits in
177    /// [`RuntimeEventMask::ALL_MAP_EVENTS`](crate::RuntimeEventMask::ALL_MAP_EVENTS),
178    /// so [`RuntimeEventMask::ALL`](crate::RuntimeEventMask::ALL) selects every
179    /// map-originated type. Narrowing gates later events and keeps queued ones.
180    /// A bit outside `ALL` is an invalid-argument error.
181    pub fn set_event_mask(&self, mask: RuntimeEventMask) -> Result<()> {
182        let map = self.inner.native()?;
183        // SAFETY: map is live. The C API validates unknown mask bits.
184        maplibre_core::check(unsafe { sys::mln_map_set_event_mask(map, mask.bits()) })
185    }
186
187    /// Reports which map-originated event types this map queues, starting from
188    /// the mask its creation options selected.
189    pub fn event_mask(&self) -> Result<RuntimeEventMask> {
190        let map = self.inner.native()?;
191        let mut raw = 0;
192        // SAFETY: map is live and out_mask points to writable u64 storage.
193        maplibre_core::check(unsafe { sys::mln_map_get_event_mask(map, &mut raw) })?;
194        Ok(RuntimeEventMask::from_bits_retain(raw))
195    }
196
197    /// Applies MapLibre debug overlay mask bits.
198    pub fn set_debug_options(&self, options: MapDebugOptions) -> Result<()> {
199        let map = self.inner.native()?;
200        // SAFETY: map is live. The C API validates unknown mask bits.
201        maplibre_core::check(unsafe { sys::mln_map_set_debug_options(map, options.bits()) })
202    }
203
204    /// Reads MapLibre debug overlay mask bits.
205    pub fn debug_options(&self) -> Result<MapDebugOptions> {
206        let map = self.inner.native()?;
207        let mut raw = 0;
208        // SAFETY: map is live and out_options points to writable u32 storage.
209        maplibre_core::check(unsafe { sys::mln_map_get_debug_options(map, &mut raw) })?;
210        Ok(MapDebugOptions::from_bits_retain(raw))
211    }
212
213    /// Enables or disables MapLibre's rendering stats overlay view.
214    pub fn set_rendering_stats_view_enabled(&self, enabled: bool) -> Result<()> {
215        let map = self.inner.native()?;
216        // SAFETY: map is live and enabled is passed by value.
217        maplibre_core::check(unsafe { sys::mln_map_set_rendering_stats_view_enabled(map, enabled) })
218    }
219
220    /// Reads whether MapLibre's rendering stats overlay view is enabled.
221    pub fn rendering_stats_view_enabled(&self) -> Result<bool> {
222        let map = self.inner.native()?;
223        let mut enabled = false;
224        // SAFETY: map is live and out_enabled points to writable bool storage.
225        maplibre_core::check(unsafe {
226            sys::mln_map_get_rendering_stats_view_enabled(map, &mut enabled)
227        })?;
228        Ok(enabled)
229    }
230
231    /// Reads whether MapLibre currently considers the map fully loaded.
232    pub fn is_fully_loaded(&self) -> Result<bool> {
233        let map = self.inner.native()?;
234        let mut loaded = false;
235        // SAFETY: map is live and out_loaded points to writable bool storage.
236        maplibre_core::check(unsafe { sys::mln_map_is_fully_loaded(map, &mut loaded) })?;
237        Ok(loaded)
238    }
239
240    /// Dumps map debug logs through MapLibre Native logging.
241    pub fn dump_debug_logs(&self) -> Result<()> {
242        let map = self.inner.native()?;
243        // SAFETY: map is live.
244        maplibre_core::check(unsafe { sys::mln_map_dump_debug_logs(map) })
245    }
246
247    /// Reads the map's logical viewport size in UI pixels and its pixel ratio.
248    /// The scale factor is fixed for the lifetime of the map and independent of
249    /// any render target's scale factor.
250    pub fn size(&self) -> Result<(u32, u32, f64)> {
251        let map = self.inner.native()?;
252        let mut width = 0u32;
253        let mut height = 0u32;
254        let mut scale_factor = 0f64;
255        // SAFETY: map is live and all three out pointers reference live locals
256        // for the duration of the call.
257        maplibre_core::check(unsafe {
258            sys::mln_map_get_size(map, &mut width, &mut height, &mut scale_factor)
259        })?;
260        Ok((width, height, scale_factor))
261    }
262
263    /// Reads live viewport and render-transform controls.
264    pub fn viewport_options(&self) -> Result<MapViewportOptions> {
265        let map = self.inner.native()?;
266        // SAFETY: Default constructor takes no arguments and initializes size.
267        let mut raw = unsafe { sys::mln_map_viewport_options_default() };
268        // SAFETY: map is live and raw has a valid size field for C to fill.
269        maplibre_core::check(unsafe { sys::mln_map_get_viewport_options(map, &mut raw) })?;
270        Ok(MapViewportOptions::from_native(raw))
271    }
272
273    /// Applies selected live viewport and render-transform controls.
274    pub fn set_viewport_options(&self, options: &MapViewportOptions) -> Result<()> {
275        let map = self.inner.native()?;
276        let raw = options.to_native();
277        // SAFETY: map is live and raw is a materialized descriptor valid for
278        // the duration of this call.
279        maplibre_core::check(unsafe { sys::mln_map_set_viewport_options(map, &raw) })
280    }
281
282    /// Reads tile prefetch and LOD tuning controls.
283    pub fn tile_options(&self) -> Result<MapTileOptions> {
284        let map = self.inner.native()?;
285        // SAFETY: Default constructor takes no arguments and initializes size.
286        let mut raw = unsafe { sys::mln_map_tile_options_default() };
287        // SAFETY: map is live and raw has a valid size field for C to fill.
288        maplibre_core::check(unsafe { sys::mln_map_get_tile_options(map, &mut raw) })?;
289        Ok(MapTileOptions::from_native(raw))
290    }
291
292    /// Applies selected tile prefetch and LOD tuning controls.
293    pub fn set_tile_options(&self, options: &MapTileOptions) -> Result<()> {
294        let map = self.inner.native()?;
295        let raw = options.to_native();
296        // SAFETY: map is live and raw is a materialized descriptor valid for
297        // the duration of this call.
298        maplibre_core::check(unsafe { sys::mln_map_set_tile_options(map, &raw) })
299    }
300
301    /// Reads the current camera snapshot.
302    pub fn camera(&self) -> Result<CameraOptions> {
303        let map = self.inner.native()?;
304        // SAFETY: Default constructor takes no arguments and initializes size.
305        let mut raw = unsafe { sys::mln_camera_options_default() };
306        // SAFETY: map is live and raw has a valid size field for C to fill.
307        maplibre_core::check(unsafe { sys::mln_map_get_camera(map, &mut raw) })?;
308        Ok(CameraOptions::from_native(raw))
309    }
310
311    /// Applies a camera jump command.
312    pub fn jump_to(&self, camera: &CameraOptions) -> Result<()> {
313        let map = self.inner.native()?;
314        let raw = camera.to_native();
315        // SAFETY: map is live and raw is a materialized descriptor valid for
316        // the duration of this call.
317        maplibre_core::check(unsafe { sys::mln_map_jump_to(map, &raw) })
318    }
319
320    /// Applies a camera ease transition command. An absent `animation`, or one
321    /// with no duration, reaches the target before this call returns.
322    pub fn ease_to(
323        &self,
324        camera: &CameraOptions,
325        animation: Option<&AnimationOptions>,
326    ) -> Result<()> {
327        let map = self.inner.native()?;
328        let raw_camera = camera.to_native();
329        let raw_animation = animation.map(AnimationOptions::to_native);
330        // SAFETY: map is live and descriptors are valid for this call. A null
331        // animation pointer requests native defaults.
332        maplibre_core::check(unsafe {
333            sys::mln_map_ease_to(map, &raw_camera, option_ptr(raw_animation.as_ref()))
334        })
335    }
336
337    /// Applies a camera fly transition command. Fly animates by default, so the
338    /// camera is still en route when this call returns and advances as the
339    /// runtime is pumped.
340    pub fn fly_to(
341        &self,
342        camera: &CameraOptions,
343        animation: Option<&AnimationOptions>,
344    ) -> Result<()> {
345        let map = self.inner.native()?;
346        let raw_camera = camera.to_native();
347        let raw_animation = animation.map(AnimationOptions::to_native);
348        // SAFETY: map is live and descriptors are valid for this call. A null
349        // animation pointer requests native defaults.
350        maplibre_core::check(unsafe {
351            sys::mln_map_fly_to(map, &raw_camera, option_ptr(raw_animation.as_ref()))
352        })
353    }
354
355    /// Applies a screen-space pan command.
356    pub fn move_by(&self, delta_x: f64, delta_y: f64) -> Result<()> {
357        let map = self.inner.native()?;
358        // SAFETY: map is live. The C API validates numeric values.
359        maplibre_core::check(unsafe { sys::mln_map_move_by(map, delta_x, delta_y) })
360    }
361
362    /// Applies an animated screen-space pan command.
363    /// An absent `animation`, or one with no duration, applies the pan
364    /// instantly.
365    pub fn move_by_animated(
366        &self,
367        delta_x: f64,
368        delta_y: f64,
369        animation: Option<&AnimationOptions>,
370    ) -> Result<()> {
371        let map = self.inner.native()?;
372        let raw_animation = animation.map(AnimationOptions::to_native);
373        // SAFETY: map is live and the optional animation descriptor is valid
374        // for this call. The C API validates numeric values.
375        maplibre_core::check(unsafe {
376            sys::mln_map_move_by_animated(map, delta_x, delta_y, option_ptr(raw_animation.as_ref()))
377        })
378    }
379
380    /// Applies a screen-space zoom command.
381    pub fn scale_by(&self, scale: f64, anchor: Option<ScreenPoint>) -> Result<()> {
382        let map = self.inner.native()?;
383        let raw_anchor = anchor.map(ScreenPoint::to_native);
384        // SAFETY: map is live and the optional anchor pointer is valid for this
385        // call. The C API validates numeric values.
386        maplibre_core::check(unsafe {
387            sys::mln_map_scale_by(map, scale, option_ptr(raw_anchor.as_ref()))
388        })
389    }
390
391    /// Applies an animated screen-space zoom command.
392    /// An absent `animation`, or one with no duration, applies the zoom
393    /// instantly.
394    pub fn scale_by_animated(
395        &self,
396        scale: f64,
397        anchor: Option<ScreenPoint>,
398        animation: Option<&AnimationOptions>,
399    ) -> Result<()> {
400        let map = self.inner.native()?;
401        let raw_anchor = anchor.map(ScreenPoint::to_native);
402        let raw_animation = animation.map(AnimationOptions::to_native);
403        // SAFETY: map is live and optional descriptors are valid for this call.
404        // The C API validates numeric values.
405        maplibre_core::check(unsafe {
406            sys::mln_map_scale_by_animated(
407                map,
408                scale,
409                option_ptr(raw_anchor.as_ref()),
410                option_ptr(raw_animation.as_ref()),
411            )
412        })
413    }
414
415    /// Applies a screen-space rotate command.
416    pub fn rotate_by(&self, first: ScreenPoint, second: ScreenPoint) -> Result<()> {
417        let map = self.inner.native()?;
418        // SAFETY: map is live. Points are passed by value and validated by C.
419        maplibre_core::check(unsafe {
420            sys::mln_map_rotate_by(map, first.to_native(), second.to_native())
421        })
422    }
423
424    /// Applies an animated screen-space rotate command.
425    /// An absent `animation`, or one with no duration, applies the rotation
426    /// instantly.
427    pub fn rotate_by_animated(
428        &self,
429        first: ScreenPoint,
430        second: ScreenPoint,
431        animation: Option<&AnimationOptions>,
432    ) -> Result<()> {
433        let map = self.inner.native()?;
434        let raw_animation = animation.map(AnimationOptions::to_native);
435        // SAFETY: map is live and optional animation descriptor is valid for
436        // this call. Points are passed by value and validated by C.
437        maplibre_core::check(unsafe {
438            sys::mln_map_rotate_by_animated(
439                map,
440                first.to_native(),
441                second.to_native(),
442                option_ptr(raw_animation.as_ref()),
443            )
444        })
445    }
446
447    /// Applies a pitch delta command.
448    pub fn pitch_by(&self, pitch: f64) -> Result<()> {
449        let map = self.inner.native()?;
450        // SAFETY: map is live. The C API validates numeric values.
451        maplibre_core::check(unsafe { sys::mln_map_pitch_by(map, pitch) })
452    }
453
454    /// Applies an animated pitch delta command.
455    /// An absent `animation`, or one with no duration, applies the pitch
456    /// instantly.
457    pub fn pitch_by_animated(
458        &self,
459        pitch: f64,
460        animation: Option<&AnimationOptions>,
461    ) -> Result<()> {
462        let map = self.inner.native()?;
463        let raw_animation = animation.map(AnimationOptions::to_native);
464        // SAFETY: map is live and optional animation descriptor is valid for
465        // this call. The C API validates numeric values.
466        maplibre_core::check(unsafe {
467            sys::mln_map_pitch_by_animated(map, pitch, option_ptr(raw_animation.as_ref()))
468        })
469    }
470
471    /// Cancels active camera transitions.
472    pub fn cancel_transitions(&self) -> Result<()> {
473        let map = self.inner.native()?;
474        // SAFETY: map is live.
475        maplibre_core::check(unsafe { sys::mln_map_cancel_transitions(map) })
476    }
477
478    /// Marks whether a host-driven gesture is in progress. The flag stays set
479    /// until the host clears it, so pair every `true` with a `false`.
480    pub fn set_gesture_in_progress(&self, in_progress: bool) -> Result<()> {
481        let map = self.inner.native()?;
482        // SAFETY: map is live and in_progress is passed by value.
483        maplibre_core::check(unsafe { sys::mln_map_set_gesture_in_progress(map, in_progress) })
484    }
485
486    /// Reads whether a host-driven gesture is currently in progress.
487    pub fn is_gesture_in_progress(&self) -> Result<bool> {
488        let map = self.inner.native()?;
489        let mut in_progress = false;
490        // SAFETY: map is live and out_in_progress points to writable bool
491        // storage.
492        maplibre_core::check(unsafe {
493            sys::mln_map_is_gesture_in_progress(map, &mut in_progress)
494        })?;
495        Ok(in_progress)
496    }
497
498    /// Computes a camera that fits geographic bounds in the current viewport.
499    pub fn camera_for_lat_lng_bounds(
500        &self,
501        bounds: LatLngBounds,
502        fit_options: Option<&CameraFitOptions>,
503    ) -> Result<CameraOptions> {
504        let map = self.inner.native()?;
505        let raw_fit = fit_options.map(CameraFitOptions::to_native);
506        // SAFETY: Default constructor takes no arguments and initializes size.
507        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
508        // SAFETY: map is live, bounds is passed by value, optional fit options
509        // are valid for this call, and raw_camera is writable.
510        maplibre_core::check(unsafe {
511            sys::mln_map_camera_for_lat_lng_bounds(
512                map,
513                bounds.to_native(),
514                option_ptr(raw_fit.as_ref()),
515                &mut raw_camera,
516            )
517        })?;
518        Ok(CameraOptions::from_native(raw_camera))
519    }
520
521    /// Computes a camera that fits geographic coordinates in the current viewport.
522    pub fn camera_for_lat_lngs(
523        &self,
524        coordinates: &[LatLng],
525        fit_options: Option<&CameraFitOptions>,
526    ) -> Result<CameraOptions> {
527        let map = self.inner.native()?;
528        if coordinates.is_empty() {
529            return Err(Error::invalid_argument(
530                "camera_for_lat_lngs requires at least one coordinate",
531            ));
532        }
533        let raw_coordinates = lat_lngs_to_native(coordinates);
534        let raw_fit = fit_options.map(CameraFitOptions::to_native);
535        // SAFETY: Default constructor takes no arguments and initializes size.
536        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
537        // SAFETY: map is live, arrays are valid for coordinate_count non-empty
538        // entries, optional fit options are valid, and raw_camera is writable.
539        maplibre_core::check(unsafe {
540            sys::mln_map_camera_for_lat_lngs(
541                map,
542                const_ptr_or_null(&raw_coordinates),
543                raw_coordinates.len(),
544                option_ptr(raw_fit.as_ref()),
545                &mut raw_camera,
546            )
547        })?;
548        Ok(CameraOptions::from_native(raw_camera))
549    }
550
551    /// Computes a camera that fits a geometry in the current viewport.
552    pub fn camera_for_geometry(
553        &self,
554        geometry: &[u8],
555        fit_options: Option<&CameraFitOptions>,
556    ) -> Result<CameraOptions> {
557        let map = self.inner.native()?;
558        let native_geometry = maplibre_core::string::buffer_view(geometry);
559        let raw_fit = fit_options.map(CameraFitOptions::to_native);
560        // SAFETY: Default constructor takes no arguments and initializes size.
561        let mut raw_camera = unsafe { sys::mln_camera_options_default() };
562        // SAFETY: map is live, native_geometry owns backing storage for the
563        // duration of this call, optional fit options are valid, and raw_camera
564        // is writable.
565        maplibre_core::check(unsafe {
566            sys::mln_map_camera_for_geometry(
567                map,
568                native_geometry,
569                option_ptr(raw_fit.as_ref()),
570                &mut raw_camera,
571            )
572        })?;
573        Ok(CameraOptions::from_native(raw_camera))
574    }
575
576    /// Computes geographic bounds for a camera from two viewport corners.
577    ///
578    /// The box is the hull of the top-left and bottom-right screen corners for
579    /// that camera in the current viewport. When bearing and pitch are zero, the
580    /// box equals the visible area. Those corners are the northwest and
581    /// southeast of the viewport. Longitudes stay in -180 to 180.
582    pub fn lat_lng_bounds_for_camera(&self, camera: &CameraOptions) -> Result<LatLngBounds> {
583        let map = self.inner.native()?;
584        let raw_camera = camera.to_native();
585        let mut raw_bounds = empty_bounds();
586        // SAFETY: map is live, raw_camera is a valid descriptor for this call,
587        // and raw_bounds points to writable storage.
588        maplibre_core::check(unsafe {
589            sys::mln_map_lat_lng_bounds_for_camera(map, &raw_camera, &mut raw_bounds)
590        })?;
591        Ok(LatLngBounds::from_native(raw_bounds))
592    }
593
594    /// Computes geographic bounds for a camera from the four viewport corners.
595    ///
596    /// The axis-aligned hull of all four screen corners and the center
597    /// encompasses the projected viewport. Longitudes unwrap onto the shortest
598    /// path through the center. A viewport that crosses the antimeridian reports
599    /// values outside -180 to 180.
600    pub fn lat_lng_bounds_for_camera_unwrapped(
601        &self,
602        camera: &CameraOptions,
603    ) -> Result<LatLngBounds> {
604        let map = self.inner.native()?;
605        let raw_camera = camera.to_native();
606        let mut raw_bounds = empty_bounds();
607        // SAFETY: map is live, raw_camera is a valid descriptor for this call,
608        // and raw_bounds points to writable storage.
609        maplibre_core::check(unsafe {
610            sys::mln_map_lat_lng_bounds_for_camera_unwrapped(map, &raw_camera, &mut raw_bounds)
611        })?;
612        Ok(LatLngBounds::from_native(raw_bounds))
613    }
614
615    /// Reads map camera constraint options.
616    pub fn bounds(&self) -> Result<BoundOptions> {
617        let map = self.inner.native()?;
618        // SAFETY: Default constructor takes no arguments and initializes size.
619        let mut raw = unsafe { sys::mln_bound_options_default() };
620        // SAFETY: map is live and raw has a valid size field for C to fill.
621        maplibre_core::check(unsafe { sys::mln_map_get_bounds(map, &mut raw) })?;
622        Ok(BoundOptions::from_native(raw))
623    }
624
625    /// Applies selected map camera constraint options.
626    pub fn set_bounds(&self, options: &BoundOptions) -> Result<()> {
627        let map = self.inner.native()?;
628        let raw = options.to_native();
629        // SAFETY: map is live and raw is a valid descriptor for this call.
630        maplibre_core::check(unsafe { sys::mln_map_set_bounds(map, &raw) })
631    }
632
633    /// Reads the current free camera position and orientation.
634    pub fn free_camera_options(&self) -> Result<FreeCameraOptions> {
635        let map = self.inner.native()?;
636        // SAFETY: Default constructor takes no arguments and initializes size.
637        let mut raw = unsafe { sys::mln_free_camera_options_default() };
638        // SAFETY: map is live and raw has a valid size field for C to fill.
639        maplibre_core::check(unsafe { sys::mln_map_get_free_camera_options(map, &mut raw) })?;
640        Ok(FreeCameraOptions::from_native(raw))
641    }
642
643    /// Applies selected free camera position and orientation fields.
644    pub fn set_free_camera_options(&self, options: &FreeCameraOptions) -> Result<()> {
645        let map = self.inner.native()?;
646        let raw = options.to_native();
647        // SAFETY: map is live and raw is a valid descriptor for this call.
648        maplibre_core::check(unsafe { sys::mln_map_set_free_camera_options(map, &raw) })
649    }
650
651    /// Reads current axonometric rendering options.
652    pub fn projection_mode(&self) -> Result<ProjectionMode> {
653        let map = self.inner.native()?;
654        // SAFETY: Default constructor takes no arguments and initializes size.
655        let mut raw = unsafe { sys::mln_projection_mode_default() };
656        // SAFETY: map is live and raw has a valid size field for C to fill.
657        maplibre_core::check(unsafe { sys::mln_map_get_projection_mode(map, &mut raw) })?;
658        Ok(ProjectionMode::from_native(raw))
659    }
660
661    /// Applies selected axonometric rendering option fields.
662    pub fn set_projection_mode(&self, mode: &ProjectionMode) -> Result<()> {
663        let map = self.inner.native()?;
664        let raw = mode.to_native();
665        // SAFETY: map is live and raw is a valid descriptor for this call.
666        maplibre_core::check(unsafe { sys::mln_map_set_projection_mode(map, &raw) })
667    }
668
669    /// Converts a geographic world coordinate to a screen point for the current map.
670    pub fn pixel_for_lat_lng(&self, coordinate: LatLng) -> Result<ScreenPoint> {
671        let map = self.inner.native()?;
672        let mut raw_point = empty_screen_point();
673        // SAFETY: map is live, coordinate is passed by value, and raw_point is
674        // writable storage for the output.
675        maplibre_core::check(unsafe {
676            sys::mln_map_pixel_for_lat_lng(map, coordinate.to_native(), &mut raw_point)
677        })?;
678        Ok(ScreenPoint::from_native(raw_point))
679    }
680
681    /// Converts a screen point to a geographic world coordinate for the current map.
682    pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
683        let map = self.inner.native()?;
684        let mut raw_coordinate = empty_lat_lng();
685        // SAFETY: map is live, point is passed by value, and raw_coordinate is
686        // writable storage for the output.
687        maplibre_core::check(unsafe {
688            sys::mln_map_lat_lng_for_pixel(map, point.to_native(), &mut raw_coordinate)
689        })?;
690        Ok(LatLng::from_native(raw_coordinate))
691    }
692
693    /// Converts geographic world coordinates to screen points for the current map.
694    pub fn pixels_for_lat_lngs(&self, coordinates: &[LatLng]) -> Result<Vec<ScreenPoint>> {
695        let map = self.inner.native()?;
696        let raw_coordinates = lat_lngs_to_native(coordinates);
697        let mut raw_points = vec![empty_screen_point(); coordinates.len()];
698        // SAFETY: map is live. Input and output arrays are valid for len
699        // entries, or null when len is 0.
700        maplibre_core::check(unsafe {
701            sys::mln_map_pixels_for_lat_lngs(
702                map,
703                const_ptr_or_null(&raw_coordinates),
704                raw_coordinates.len(),
705                mut_ptr_or_null(&mut raw_points),
706            )
707        })?;
708        Ok(raw_points
709            .into_iter()
710            .map(ScreenPoint::from_native)
711            .collect())
712    }
713
714    /// Converts screen points to geographic world coordinates for the current map.
715    pub fn lat_lngs_for_pixels(&self, points: &[ScreenPoint]) -> Result<Vec<LatLng>> {
716        let map = self.inner.native()?;
717        let raw_points = screen_points_to_native(points);
718        let mut raw_coordinates = vec![empty_lat_lng(); points.len()];
719        // SAFETY: map is live. Input and output arrays are valid for len
720        // entries, or null when len is 0.
721        maplibre_core::check(unsafe {
722            sys::mln_map_lat_lngs_for_pixels(
723                map,
724                const_ptr_or_null(&raw_points),
725                raw_points.len(),
726                mut_ptr_or_null(&mut raw_coordinates),
727            )
728        })?;
729        Ok(raw_coordinates
730            .into_iter()
731            .map(LatLng::from_native)
732            .collect())
733    }
734
735    /// Creates a standalone projection snapshot from the current map transform.
736    pub fn create_projection(&self) -> Result<MapProjectionHandle> {
737        MapProjectionHandle::new(self)
738    }
739
740    /// Produces a [`Send`] reference to this map for attaching a render session.
741    /// A render session is owned by the thread that attaches it, which need not
742    /// be the map's owner thread.
743    pub fn attach_ref(&self) -> Result<MapAttachRef> {
744        Ok(MapAttachRef {
745            map: self.inner.native()?,
746        })
747    }
748}
749
750/// A reference to a map for the sole purpose of attaching a render session,
751/// produced by [`MapHandle::attach_ref`]. Attaching is the one map operation
752/// that runs on the render session's thread instead of the map's.
753///
754/// This is a copied handle value and carries no Rust retention of the map.
755/// Native destroys a map only once no session is attached, and rejects a
756/// reference that outlives its map rather than binding to a later one.
757///
758/// Close the session before the map: dropping a [`MapHandle`] with a session
759/// still attached leaks the native map, reports it through
760/// [`set_leak_reporter`](crate::set_leak_reporter), and leaves the runtime
761/// undestroyable.
762#[derive(Clone, Copy)]
763pub struct MapAttachRef {
764    map: sys::mln_map,
765}
766
767impl fmt::Debug for MapAttachRef {
768    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
769        f.debug_struct("MapAttachRef").finish()
770    }
771}
772
773impl MapAttachRef {
774    /// The handle of the map this reference names.
775    pub(crate) fn map(&self) -> sys::mln_map {
776        self.map
777    }
778
779    /// Attaches a Metal native surface render target to the map.
780    ///
781    /// The layer and optional device pointers are backend-native handles. They
782    /// must name valid Metal objects for this session and remain usable on the
783    /// owner thread until the session is detached or closed.
784    pub fn attach_metal_surface(
785        &self,
786        descriptor: &MetalSurfaceDescriptor,
787    ) -> Result<RenderSessionHandle> {
788        let raw = descriptor.to_native();
789        RenderSessionHandle::attach(self, |map, out| {
790            // SAFETY: map is live, raw is a materialized descriptor valid for
791            // this call, and out is a null-initialized out-pointer.
792            unsafe { sys::mln_metal_surface_attach(map, &raw, out) }
793        })
794    }
795
796    /// Attaches a Vulkan native surface render target to the map.
797    ///
798    /// Vulkan handles are borrowed. They must remain valid and externally
799    /// synchronized until the session is detached or closed.
800    pub fn attach_vulkan_surface(
801        &self,
802        descriptor: &VulkanSurfaceDescriptor,
803    ) -> Result<RenderSessionHandle> {
804        let raw = descriptor.to_native();
805        RenderSessionHandle::attach(self, |map, out| {
806            // SAFETY: map is live, raw is a materialized descriptor valid for
807            // this call, and out is a null-initialized out-pointer.
808            unsafe { sys::mln_vulkan_surface_attach(map, &raw, out) }
809        })
810    }
811
812    /// Attaches a WebGPU native surface render target to the map.
813    ///
814    /// The surface, device, and instance are borrowed. They must stay valid
815    /// until the session detaches or closes.
816    pub fn attach_webgpu_surface(
817        &self,
818        descriptor: &WebGpuSurfaceDescriptor,
819    ) -> Result<RenderSessionHandle> {
820        let raw = descriptor.to_native();
821        RenderSessionHandle::attach(self, |map, out| {
822            // SAFETY: map is live, raw is a materialized descriptor valid for
823            // this call, and out is a null-initialized out-pointer.
824            unsafe { sys::mln_webgpu_surface_attach(map, &raw, out) }
825        })
826    }
827
828    /// Attaches an OpenGL native surface render target to the map.
829    ///
830    /// OpenGL context provider and surface handles are borrowed. They must
831    /// remain valid and externally synchronized until the session is detached
832    /// or closed.
833    pub fn attach_opengl_surface(
834        &self,
835        descriptor: &OpenGLSurfaceDescriptor,
836    ) -> Result<RenderSessionHandle> {
837        let raw = descriptor.to_native();
838        RenderSessionHandle::attach(self, |map, out| {
839            // SAFETY: map is live, raw is a materialized descriptor valid for
840            // this call, and out is a null-initialized out-pointer.
841            unsafe { sys::mln_opengl_surface_attach(map, &raw, out) }
842        })
843    }
844
845    /// Attaches a Metal session-owned texture render target to the map.
846    ///
847    /// The device pointer must name a valid Metal device that remains usable on
848    /// the owner thread until the session is detached or closed.
849    pub fn attach_metal_owned_texture(
850        &self,
851        descriptor: &MetalOwnedTextureDescriptor,
852    ) -> Result<RenderSessionHandle> {
853        let raw = descriptor.to_native();
854        RenderSessionHandle::attach(self, |map, out| {
855            // SAFETY: map is live, raw is a materialized descriptor valid for
856            // this call, and out is a null-initialized out-pointer.
857            unsafe { sys::mln_metal_owned_texture_attach(map, &raw, out) }
858        })
859    }
860
861    /// Attaches a Metal caller-owned texture render target to the map.
862    ///
863    /// The texture pointer is borrowed. The caller owns the texture, keeps it
864    /// valid until detach or close, and synchronizes use outside this session.
865    pub fn attach_metal_borrowed_texture(
866        &self,
867        descriptor: &MetalBorrowedTextureDescriptor,
868    ) -> Result<RenderSessionHandle> {
869        let raw = descriptor.to_native();
870        RenderSessionHandle::attach(self, |map, out| {
871            // SAFETY: map is live, raw is a materialized descriptor valid for
872            // this call, and out is a null-initialized out-pointer.
873            unsafe { sys::mln_metal_borrowed_texture_attach(map, &raw, out) }
874        })
875    }
876
877    /// Attaches a Vulkan session-owned texture render target to the map.
878    ///
879    /// Vulkan device and queue handles are borrowed. They must remain valid and
880    /// externally synchronized until the session is detached or closed.
881    pub fn attach_vulkan_owned_texture(
882        &self,
883        descriptor: &VulkanOwnedTextureDescriptor,
884    ) -> Result<RenderSessionHandle> {
885        let raw = descriptor.to_native();
886        RenderSessionHandle::attach(self, |map, out| {
887            // SAFETY: map is live, raw is a materialized descriptor valid for
888            // this call, and out is a null-initialized out-pointer.
889            unsafe { sys::mln_vulkan_owned_texture_attach(map, &raw, out) }
890        })
891    }
892
893    /// Attaches a Vulkan caller-owned texture render target to the map.
894    ///
895    /// Vulkan handles, image, and image view are borrowed. The caller owns the
896    /// image resources, keeps them valid until detach or close, and handles
897    /// queue-family ownership and synchronization outside this session.
898    pub fn attach_vulkan_borrowed_texture(
899        &self,
900        descriptor: &VulkanBorrowedTextureDescriptor,
901    ) -> Result<RenderSessionHandle> {
902        let raw = descriptor.to_native();
903        RenderSessionHandle::attach(self, |map, out| {
904            // SAFETY: map is live, raw is a materialized descriptor valid for
905            // this call, and out is a null-initialized out-pointer.
906            unsafe { sys::mln_vulkan_borrowed_texture_attach(map, &raw, out) }
907        })
908    }
909
910    /// Attaches a WebGPU session-owned texture render target to the map.
911    ///
912    /// The WebGPU device and queue are borrowed. They must remain valid until
913    /// the session is detached or closed, and the session renders on the thread
914    /// that owns them.
915    pub fn attach_webgpu_owned_texture(
916        &self,
917        descriptor: &WebGpuOwnedTextureDescriptor,
918    ) -> Result<RenderSessionHandle> {
919        let raw = descriptor.to_native();
920        RenderSessionHandle::attach(self, |map, out| {
921            // SAFETY: map is live, raw is a materialized descriptor valid for
922            // this call, and out is a null-initialized out-pointer.
923            unsafe { sys::mln_webgpu_owned_texture_attach(map, &raw, out) }
924        })
925    }
926
927    /// Attaches a WebGPU caller-owned texture render target to the map.
928    ///
929    /// The texture and its view are borrowed. The caller owns them, keeps them
930    /// valid until detach or close, and creates both from the descriptor's
931    /// device.
932    pub fn attach_webgpu_borrowed_texture(
933        &self,
934        descriptor: &WebGpuBorrowedTextureDescriptor,
935    ) -> Result<RenderSessionHandle> {
936        let raw = descriptor.to_native();
937        RenderSessionHandle::attach(self, |map, out| {
938            // SAFETY: map is live, raw is a materialized descriptor valid for
939            // this call, and out is a null-initialized out-pointer.
940            unsafe { sys::mln_webgpu_borrowed_texture_attach(map, &raw, out) }
941        })
942    }
943
944    /// Attaches an OpenGL session-owned texture render target to the map.
945    ///
946    /// The context provider handles are borrowed. They must remain valid until
947    /// the session is detached or closed. Host sampling must use a context in
948    /// the same share group while the acquired frame remains open.
949    pub fn attach_opengl_owned_texture(
950        &self,
951        descriptor: &OpenGLOwnedTextureDescriptor,
952    ) -> Result<RenderSessionHandle> {
953        let raw = descriptor.to_native();
954        RenderSessionHandle::attach(self, |map, out| {
955            // SAFETY: map is live, raw is a materialized descriptor valid for
956            // this call, and out is a null-initialized out-pointer.
957            unsafe { sys::mln_opengl_owned_texture_attach(map, &raw, out) }
958        })
959    }
960
961    /// Attaches an OpenGL caller-owned texture render target to the map.
962    ///
963    /// The context provider handles and texture object are borrowed. The caller
964    /// owns the texture, keeps it valid until detach or close, and synchronizes
965    /// use outside this session.
966    pub fn attach_opengl_borrowed_texture(
967        &self,
968        descriptor: &OpenGLBorrowedTextureDescriptor,
969    ) -> Result<RenderSessionHandle> {
970        let raw = descriptor.to_native();
971        RenderSessionHandle::attach(self, |map, out| {
972            // SAFETY: map is live, raw is a materialized descriptor valid for
973            // this call, and out is a null-initialized out-pointer.
974            unsafe { sys::mln_opengl_borrowed_texture_attach(map, &raw, out) }
975        })
976    }
977}
978
979#[cfg(test)]
980mod tests;