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, StyleLayerInfo, StyleLayerVisibility, StyleTransitionOptions, TileJsonInfo,
45    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}
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    ///
683    /// The longitude is wrapped to the range from -180 to 180 degrees.
684    pub fn lat_lng_for_pixel(&self, point: ScreenPoint) -> Result<LatLng> {
685        let map = self.inner.native()?;
686        let mut raw_coordinate = empty_lat_lng();
687        // SAFETY: map is live, point is passed by value, and raw_coordinate is
688        // writable storage for the output.
689        maplibre_core::check(unsafe {
690            sys::mln_map_lat_lng_for_pixel(map, point.to_native(), &mut raw_coordinate)
691        })?;
692        Ok(LatLng::from_native(raw_coordinate))
693    }
694
695    /// Converts a screen point to an unwrapped geographic coordinate.
696    ///
697    /// The longitude preserves the visible world copy and may fall outside
698    /// -180 to 180.
699    pub fn lat_lng_for_pixel_unwrapped(&self, point: ScreenPoint) -> Result<LatLng> {
700        let map = self.inner.native()?;
701        let mut raw_coordinate = empty_lat_lng();
702        // SAFETY: map is live, point is passed by value, and raw_coordinate is
703        // writable storage for the output.
704        maplibre_core::check(unsafe {
705            sys::mln_map_lat_lng_for_pixel_unwrapped(map, point.to_native(), &mut raw_coordinate)
706        })?;
707        Ok(LatLng::from_native(raw_coordinate))
708    }
709
710    /// Reads the ground distance in meters covered by one logical map pixel at
711    /// a latitude for the current map zoom.
712    pub fn meters_per_pixel_at_latitude(&self, latitude: f64) -> Result<f64> {
713        let map = self.inner.native()?;
714        let mut meters_per_pixel = 0.0;
715        // SAFETY: map is live and meters_per_pixel is writable storage for the
716        // output.
717        maplibre_core::check(unsafe {
718            sys::mln_map_meters_per_pixel_at_latitude(map, latitude, &mut meters_per_pixel)
719        })?;
720        Ok(meters_per_pixel)
721    }
722
723    /// Converts geographic world coordinates to screen points for the current map.
724    pub fn pixels_for_lat_lngs(&self, coordinates: &[LatLng]) -> Result<Vec<ScreenPoint>> {
725        let map = self.inner.native()?;
726        let raw_coordinates = lat_lngs_to_native(coordinates);
727        let mut raw_points = vec![empty_screen_point(); coordinates.len()];
728        // SAFETY: map is live. Input and output arrays are valid for len
729        // entries, or null when len is 0.
730        maplibre_core::check(unsafe {
731            sys::mln_map_pixels_for_lat_lngs(
732                map,
733                const_ptr_or_null(&raw_coordinates),
734                raw_coordinates.len(),
735                mut_ptr_or_null(&mut raw_points),
736            )
737        })?;
738        Ok(raw_points
739            .into_iter()
740            .map(ScreenPoint::from_native)
741            .collect())
742    }
743
744    /// Converts screen points to geographic world coordinates for the current map.
745    ///
746    /// Each longitude is wrapped to the range from -180 to 180 degrees.
747    pub fn lat_lngs_for_pixels(&self, points: &[ScreenPoint]) -> Result<Vec<LatLng>> {
748        let map = self.inner.native()?;
749        let raw_points = screen_points_to_native(points);
750        let mut raw_coordinates = vec![empty_lat_lng(); points.len()];
751        // SAFETY: map is live. Input and output arrays are valid for len
752        // entries, or null when len is 0.
753        maplibre_core::check(unsafe {
754            sys::mln_map_lat_lngs_for_pixels(
755                map,
756                const_ptr_or_null(&raw_points),
757                raw_points.len(),
758                mut_ptr_or_null(&mut raw_coordinates),
759            )
760        })?;
761        Ok(raw_coordinates
762            .into_iter()
763            .map(LatLng::from_native)
764            .collect())
765    }
766
767    /// Converts screen points to unwrapped geographic coordinates.
768    ///
769    /// Each longitude preserves its visible world copy and may fall outside
770    /// -180 to 180.
771    pub fn lat_lngs_for_pixels_unwrapped(&self, points: &[ScreenPoint]) -> Result<Vec<LatLng>> {
772        let map = self.inner.native()?;
773        let raw_points = screen_points_to_native(points);
774        let mut raw_coordinates = vec![empty_lat_lng(); points.len()];
775        // SAFETY: map is live. Input and output arrays are valid for len
776        // entries, or null when len is 0.
777        maplibre_core::check(unsafe {
778            sys::mln_map_lat_lngs_for_pixels_unwrapped(
779                map,
780                const_ptr_or_null(&raw_points),
781                raw_points.len(),
782                mut_ptr_or_null(&mut raw_coordinates),
783            )
784        })?;
785        Ok(raw_coordinates
786            .into_iter()
787            .map(LatLng::from_native)
788            .collect())
789    }
790
791    /// Creates a standalone projection snapshot from the current map transform.
792    pub fn create_projection(&self) -> Result<MapProjectionHandle> {
793        MapProjectionHandle::new(self)
794    }
795
796    /// Produces a [`Send`] reference to this map for attaching a render session.
797    /// A render session is owned by the thread that attaches it, which need not
798    /// be the map's owner thread.
799    pub fn attach_ref(&self) -> Result<MapAttachRef> {
800        Ok(MapAttachRef {
801            map: self.inner.native()?,
802        })
803    }
804}
805
806/// A reference to a map for the sole purpose of attaching a render session,
807/// produced by [`MapHandle::attach_ref`]. Attaching is the one map operation
808/// that runs on the render session's thread instead of the map's.
809///
810/// This is a copied handle value and carries no Rust retention of the map.
811/// Native destroys a map only once no session is attached, and rejects a
812/// reference that outlives its map rather than binding to a later one.
813///
814/// Close the session before the map: dropping a [`MapHandle`] with a session
815/// still attached leaks the native map, reports it through
816/// [`set_leak_reporter`](crate::set_leak_reporter), and leaves the runtime
817/// undestroyable.
818#[derive(Clone, Copy)]
819pub struct MapAttachRef {
820    map: sys::mln_map,
821}
822
823impl fmt::Debug for MapAttachRef {
824    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825        f.debug_struct("MapAttachRef").finish()
826    }
827}
828
829impl MapAttachRef {
830    /// The handle of the map this reference names.
831    pub(crate) fn map(&self) -> sys::mln_map {
832        self.map
833    }
834
835    /// Attaches a Metal native surface render target to the map.
836    ///
837    /// The layer and optional device pointers are backend-native handles. They
838    /// must name valid Metal objects for this session and remain usable on the
839    /// owner thread until the session is detached or closed.
840    pub fn attach_metal_surface(
841        &self,
842        descriptor: &MetalSurfaceDescriptor,
843    ) -> Result<RenderSessionHandle> {
844        let raw = descriptor.to_native();
845        RenderSessionHandle::attach(self, |map, out| {
846            // SAFETY: map is live, raw is a materialized descriptor valid for
847            // this call, and out is a null-initialized out-pointer.
848            unsafe { sys::mln_metal_surface_attach(map, &raw, out) }
849        })
850    }
851
852    /// Attaches a Vulkan native surface render target to the map.
853    ///
854    /// Vulkan handles are borrowed. They must remain valid and externally
855    /// synchronized until the session is detached or closed.
856    pub fn attach_vulkan_surface(
857        &self,
858        descriptor: &VulkanSurfaceDescriptor,
859    ) -> Result<RenderSessionHandle> {
860        let raw = descriptor.to_native();
861        RenderSessionHandle::attach(self, |map, out| {
862            // SAFETY: map is live, raw is a materialized descriptor valid for
863            // this call, and out is a null-initialized out-pointer.
864            unsafe { sys::mln_vulkan_surface_attach(map, &raw, out) }
865        })
866    }
867
868    /// Attaches a WebGPU native surface render target to the map.
869    ///
870    /// The surface, device, and instance are borrowed. They must stay valid
871    /// until the session detaches or closes.
872    pub fn attach_webgpu_surface(
873        &self,
874        descriptor: &WebGpuSurfaceDescriptor,
875    ) -> Result<RenderSessionHandle> {
876        let raw = descriptor.to_native();
877        RenderSessionHandle::attach(self, |map, out| {
878            // SAFETY: map is live, raw is a materialized descriptor valid for
879            // this call, and out is a null-initialized out-pointer.
880            unsafe { sys::mln_webgpu_surface_attach(map, &raw, out) }
881        })
882    }
883
884    /// Attaches an OpenGL native surface render target to the map.
885    ///
886    /// OpenGL context provider and surface handles are borrowed. They must
887    /// remain valid and externally synchronized until the session is detached
888    /// or closed.
889    pub fn attach_opengl_surface(
890        &self,
891        descriptor: &OpenGLSurfaceDescriptor,
892    ) -> Result<RenderSessionHandle> {
893        let raw = descriptor.to_native();
894        RenderSessionHandle::attach(self, |map, out| {
895            // SAFETY: map is live, raw is a materialized descriptor valid for
896            // this call, and out is a null-initialized out-pointer.
897            unsafe { sys::mln_opengl_surface_attach(map, &raw, out) }
898        })
899    }
900
901    /// Attaches a Metal session-owned texture render target to the map.
902    ///
903    /// The device pointer must name a valid Metal device that remains usable on
904    /// the owner thread until the session is detached or closed.
905    pub fn attach_metal_owned_texture(
906        &self,
907        descriptor: &MetalOwnedTextureDescriptor,
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_metal_owned_texture_attach(map, &raw, out) }
914        })
915    }
916
917    /// Attaches a Metal caller-owned texture render target to the map.
918    ///
919    /// The texture pointer is borrowed. The caller owns the texture, keeps it
920    /// valid until detach or close, and synchronizes use outside this session.
921    pub fn attach_metal_borrowed_texture(
922        &self,
923        descriptor: &MetalBorrowedTextureDescriptor,
924    ) -> Result<RenderSessionHandle> {
925        let raw = descriptor.to_native();
926        RenderSessionHandle::attach(self, |map, out| {
927            // SAFETY: map is live, raw is a materialized descriptor valid for
928            // this call, and out is a null-initialized out-pointer.
929            unsafe { sys::mln_metal_borrowed_texture_attach(map, &raw, out) }
930        })
931    }
932
933    /// Attaches a Vulkan session-owned texture render target to the map.
934    ///
935    /// Vulkan device and queue handles are borrowed. They must remain valid and
936    /// externally synchronized until the session is detached or closed.
937    pub fn attach_vulkan_owned_texture(
938        &self,
939        descriptor: &VulkanOwnedTextureDescriptor,
940    ) -> Result<RenderSessionHandle> {
941        let raw = descriptor.to_native();
942        RenderSessionHandle::attach(self, |map, out| {
943            // SAFETY: map is live, raw is a materialized descriptor valid for
944            // this call, and out is a null-initialized out-pointer.
945            unsafe { sys::mln_vulkan_owned_texture_attach(map, &raw, out) }
946        })
947    }
948
949    /// Attaches a Vulkan caller-owned texture render target to the map.
950    ///
951    /// Vulkan handles, image, and image view are borrowed. The caller owns the
952    /// image resources, keeps them valid until detach or close, and handles
953    /// queue-family ownership and synchronization outside this session.
954    pub fn attach_vulkan_borrowed_texture(
955        &self,
956        descriptor: &VulkanBorrowedTextureDescriptor,
957    ) -> Result<RenderSessionHandle> {
958        let raw = descriptor.to_native();
959        RenderSessionHandle::attach(self, |map, out| {
960            // SAFETY: map is live, raw is a materialized descriptor valid for
961            // this call, and out is a null-initialized out-pointer.
962            unsafe { sys::mln_vulkan_borrowed_texture_attach(map, &raw, out) }
963        })
964    }
965
966    /// Attaches a WebGPU session-owned texture render target to the map.
967    ///
968    /// The WebGPU device and queue are borrowed. They must remain valid until
969    /// the session is detached or closed, and the session renders on the thread
970    /// that owns them.
971    pub fn attach_webgpu_owned_texture(
972        &self,
973        descriptor: &WebGpuOwnedTextureDescriptor,
974    ) -> Result<RenderSessionHandle> {
975        let raw = descriptor.to_native();
976        RenderSessionHandle::attach(self, |map, out| {
977            // SAFETY: map is live, raw is a materialized descriptor valid for
978            // this call, and out is a null-initialized out-pointer.
979            unsafe { sys::mln_webgpu_owned_texture_attach(map, &raw, out) }
980        })
981    }
982
983    /// Attaches a WebGPU caller-owned texture render target to the map.
984    ///
985    /// The texture and its view are borrowed. The caller owns them, keeps them
986    /// valid until detach or close, and creates both from the descriptor's
987    /// device.
988    pub fn attach_webgpu_borrowed_texture(
989        &self,
990        descriptor: &WebGpuBorrowedTextureDescriptor,
991    ) -> Result<RenderSessionHandle> {
992        let raw = descriptor.to_native();
993        RenderSessionHandle::attach(self, |map, out| {
994            // SAFETY: map is live, raw is a materialized descriptor valid for
995            // this call, and out is a null-initialized out-pointer.
996            unsafe { sys::mln_webgpu_borrowed_texture_attach(map, &raw, out) }
997        })
998    }
999
1000    /// Attaches an OpenGL session-owned texture render target to the map.
1001    ///
1002    /// The context provider handles are borrowed. They must remain valid until
1003    /// the session is detached or closed. Host sampling must use a context in
1004    /// the same share group while the acquired frame remains open.
1005    pub fn attach_opengl_owned_texture(
1006        &self,
1007        descriptor: &OpenGLOwnedTextureDescriptor,
1008    ) -> Result<RenderSessionHandle> {
1009        let raw = descriptor.to_native();
1010        RenderSessionHandle::attach(self, |map, out| {
1011            // SAFETY: map is live, raw is a materialized descriptor valid for
1012            // this call, and out is a null-initialized out-pointer.
1013            unsafe { sys::mln_opengl_owned_texture_attach(map, &raw, out) }
1014        })
1015    }
1016
1017    /// Attaches an OpenGL caller-owned texture render target to the map.
1018    ///
1019    /// The context provider handles and texture object are borrowed. The caller
1020    /// owns the texture, keeps it valid until detach or close, and synchronizes
1021    /// use outside this session.
1022    pub fn attach_opengl_borrowed_texture(
1023        &self,
1024        descriptor: &OpenGLBorrowedTextureDescriptor,
1025    ) -> Result<RenderSessionHandle> {
1026        let raw = descriptor.to_native();
1027        RenderSessionHandle::attach(self, |map, out| {
1028            // SAFETY: map is live, raw is a materialized descriptor valid for
1029            // this call, and out is a null-initialized out-pointer.
1030            unsafe { sys::mln_opengl_borrowed_texture_attach(map, &raw, out) }
1031        })
1032    }
1033}
1034
1035#[cfg(test)]
1036mod tests;