Skip to main content

maplibre_native/
lib.rs

1//! Safe Rust binding for the MapLibre Native C API.
2//!
3//! This crate owns Rust-specific ergonomics and safety policy: thread-affine
4//! public handles, parent retention, owner-thread `Drop`, Rust errors,
5//! callback closure APIs, and lifetime-scoped render resources. Shared C ABI
6//! adaptation lives in `maplibre-native-core`.
7
8#![deny(unsafe_op_in_unsafe_fn)]
9
10mod camera;
11mod custom_geometry;
12mod events;
13mod geojson;
14mod geometry;
15mod handle;
16mod json;
17mod logging;
18mod map;
19mod options;
20mod projection;
21mod render;
22mod resource;
23mod runtime;
24mod values;
25
26use crate::values::NativeValue;
27use maplibre_native_core as maplibre_core;
28use maplibre_native_sys as sys;
29
30pub use camera::{
31    AnimationOptions, BoundOptions, CameraFitOptions, CameraOptions, FreeCameraOptions,
32    ProjectionMode,
33};
34pub use custom_geometry::{CanonicalTileId, CustomGeometrySourceOptions};
35pub use events::{
36    MapId, OfflineOperationCompletedEvent, OfflineRegionResponseErrorEvent, OfflineRegionStatus,
37    OfflineRegionStatusEvent, OfflineRegionTileCountLimitEvent, RenderFrameEvent, RenderMapEvent,
38    RenderingStats, RuntimeEvent, RuntimeEventPayload, RuntimeEventSource, StyleImageMissingEvent,
39    TileActionEvent, TileId, UnknownRuntimeEventPayload,
40};
41pub use geojson::{Feature, FeatureIdentifier, GeoJson};
42pub use geometry::Geometry;
43pub use json::{JsonMember, JsonValue};
44pub use logging::{
45    LogRecord, clear_log_callback, restore_default_async_log_severity_mask,
46    set_async_log_severity_mask, set_log_callback,
47};
48pub use map::{
49    LocationIndicatorImageKind, MapHandle, RasterDemEncoding, SourceInfo, SourceType, StyleImage,
50    StyleImageInfo, StyleImageOptions, TileScheme, TileSourceOptions, VectorTileEncoding,
51};
52pub use maplibre_core::{
53    AmbientCacheOperation, ConstrainMode, Error, ErrorKind, LogEvent, LogSeverity, LogSeverityMask,
54    MapDebugOptions, MapMode, MapOptions, MapTileOptions, MapViewportOptions, NetworkStatus,
55    NorthOrientation, OfflineOperationKind, OfflineOperationResultKind, OfflineRegionDownloadState,
56    OpenGLContextProviderMask, RenderBackendMask, RenderMode, ResourceErrorReason, ResourceKind,
57    ResourceLoadingMethod, ResourcePriority, ResourceResponseStatus, ResourceStoragePolicy,
58    ResourceUsage, Result, RuntimeEventType, TileLodMode, TileOperation, ViewportMode,
59};
60pub use projection::MapProjectionHandle;
61pub use render::{
62    DetachedRenderSessionHandle, EglContextDescriptor, FeatureExtensionResult,
63    FeatureStateSelector, FrameNativePointer, FrameOpenGLTextureName,
64    MetalBorrowedTextureDescriptor, MetalContextDescriptor, MetalOwnedTextureDescriptor,
65    MetalOwnedTextureFrame, MetalOwnedTextureFrameHandle, MetalSurfaceDescriptor, NativePointer,
66    OpenGLBorrowedTextureDescriptor, OpenGLContextDescriptor, OpenGLOwnedTextureDescriptor,
67    OpenGLOwnedTextureFrame, OpenGLOwnedTextureFrameHandle, OpenGLSurfaceDescriptor,
68    PremultipliedRgba8Image, QueriedFeature, RenderSessionHandle, RenderTargetExtent,
69    RenderedFeatureQueryOptions, RenderedQueryGeometry, SourceFeatureQueryOptions,
70    TextureImageInfo, VulkanBorrowedTextureDescriptor, VulkanContextDescriptor,
71    VulkanOwnedTextureDescriptor, VulkanOwnedTextureFrame, VulkanOwnedTextureFrameHandle,
72    VulkanSurfaceDescriptor, WglContextDescriptor,
73};
74pub use resource::{
75    ByteRange, ResourceProviderDecision, ResourceRequest, ResourceRequestHandle, ResourceResponse,
76    ResourceTransformRequest,
77};
78pub use runtime::{
79    OfflineOperationHandle, OfflineRegionDefinition, OfflineRegionInfo, RuntimeHandle,
80    RuntimeOptions,
81};
82pub use values::{
83    EdgeInsets, LatLng, LatLngBounds, ProjectedMeters, Quaternion, ScreenBox, ScreenPoint,
84    UnitBezier, Vec3,
85};
86
87/// Error returned by consuming one-shot handle operations when the handle
88/// remains live and the operation can be retried.
89#[derive(Debug)]
90pub struct HandleOperationError<T> {
91    error: Error,
92    handle: T,
93}
94
95impl<T> HandleOperationError<T> {
96    pub(crate) fn new(error: Error, handle: T) -> Self {
97        Self { error, handle }
98    }
99
100    /// Returns the operation error.
101    pub fn error(&self) -> &Error {
102        &self.error
103    }
104
105    /// Returns the stable category for the operation error.
106    pub fn kind(&self) -> ErrorKind {
107        self.error.kind()
108    }
109
110    /// Returns the raw C status for native operation errors, when available.
111    pub fn raw_status(&self) -> Option<sys::mln_status> {
112        self.error.raw_status()
113    }
114
115    /// Returns the copied diagnostic message for the operation error.
116    pub fn diagnostic(&self) -> &str {
117        self.error.diagnostic()
118    }
119
120    /// Returns the operation error, dropping the still-live handle.
121    pub fn into_error(self) -> Error {
122        self.error
123    }
124
125    /// Returns the still-live handle so the operation can be retried.
126    pub fn into_handle(self) -> T {
127        self.handle
128    }
129
130    /// Splits this error into the operation error and still-live handle.
131    pub fn into_parts(self) -> (Error, T) {
132        (self.error, self.handle)
133    }
134}
135
136impl<T> std::fmt::Display for HandleOperationError<T> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        self.error.fmt(f)
139    }
140}
141
142impl<T: std::fmt::Debug> std::error::Error for HandleOperationError<T> {}
143
144/// Returns the native C ABI contract version.
145pub fn c_version() -> u32 {
146    // SAFETY: mln_c_version takes no arguments and returns the process-global C
147    // ABI version for the linked native library.
148    unsafe { sys::mln_c_version() }
149}
150
151/// Returns the render backends compiled into the linked native library.
152pub fn supported_render_backends() -> RenderBackendMask {
153    // SAFETY: mln_supported_render_backend_mask takes no arguments and returns a
154    // value mask. Unknown future bits are preserved by from_bits_retain.
155    let mask = unsafe { sys::mln_supported_render_backend_mask() };
156    RenderBackendMask::from_bits_retain(mask)
157}
158
159/// Returns the OpenGL context providers compiled into the linked native library.
160pub fn supported_opengl_context_providers() -> OpenGLContextProviderMask {
161    // SAFETY: mln_opengl_supported_context_provider_mask takes no arguments and
162    // returns a value mask. Unknown future bits are preserved by from_bits_retain.
163    let mask = unsafe { sys::mln_opengl_supported_context_provider_mask() };
164    OpenGLContextProviderMask::from_bits_retain(mask)
165}
166
167/// Converts a geographic coordinate to Spherical Mercator projected meters.
168pub fn projected_meters_for_lat_lng(coordinate: LatLng) -> Result<ProjectedMeters> {
169    let mut raw_meters = sys::mln_projected_meters {
170        northing: 0.0,
171        easting: 0.0,
172    };
173    // SAFETY: coordinate is passed by value. out_meters points to valid
174    // writable storage for one projected-meter value.
175    maplibre_core::check(unsafe {
176        sys::mln_projected_meters_for_lat_lng(coordinate.to_native(), &mut raw_meters)
177    })?;
178    Ok(ProjectedMeters::from_native(raw_meters))
179}
180
181/// Converts Spherical Mercator projected meters to a geographic coordinate.
182pub fn lat_lng_for_projected_meters(meters: ProjectedMeters) -> Result<LatLng> {
183    let mut raw_coordinate = sys::mln_lat_lng {
184        latitude: 0.0,
185        longitude: 0.0,
186    };
187    // SAFETY: meters is passed by value. out_coordinate points to valid
188    // writable storage for one coordinate value.
189    maplibre_core::check(unsafe {
190        sys::mln_lat_lng_for_projected_meters(meters.to_native(), &mut raw_coordinate)
191    })?;
192    Ok(LatLng::from_native(raw_coordinate))
193}
194
195/// Reads MapLibre Native's process-global network status.
196pub fn network_status() -> Result<NetworkStatus> {
197    let mut raw_status = 0;
198    // SAFETY: out_status points to valid writable storage for one u32.
199    maplibre_core::check(unsafe { sys::mln_network_status_get(&mut raw_status) })?;
200    Ok(NetworkStatus::from_raw(raw_status))
201}
202
203/// Sets MapLibre Native's process-global network status.
204pub fn set_network_status(status: NetworkStatus) -> Result<()> {
205    set_network_status_raw(status.raw()?)
206}
207
208fn set_network_status_raw(raw_status: u32) -> Result<()> {
209    // SAFETY: The raw value is passed by value. The C API validates the enum
210    // domain and reports invalid values as MLN_STATUS_INVALID_ARGUMENT.
211    maplibre_core::check(unsafe { sys::mln_network_status_set(raw_status) })
212}
213
214#[cfg(test)]
215mod tests {
216    use static_assertions::assert_not_impl_any;
217
218    use super::*;
219
220    assert_not_impl_any!(RuntimeHandle: Send, Sync);
221    assert_not_impl_any!(MapHandle: Send, Sync);
222    assert_not_impl_any!(MapProjectionHandle: Send, Sync);
223    assert_not_impl_any!(NativePointer: Send, Sync);
224    assert_not_impl_any!(FrameNativePointer<'static>: Send, Sync);
225    assert_not_impl_any!(RenderSessionHandle: Send, Sync);
226
227    #[test]
228    fn projected_meter_helpers_round_trip() {
229        let coordinate = LatLng::new(45.0, -122.0);
230        let meters = projected_meters_for_lat_lng(coordinate).unwrap();
231        let round_tripped = lat_lng_for_projected_meters(meters).unwrap();
232
233        assert!((round_tripped.latitude - coordinate.latitude).abs() < 1e-9);
234        assert!((round_tripped.longitude - coordinate.longitude).abs() < 1e-9);
235    }
236
237    #[test]
238    fn invalid_network_status_reports_public_error() {
239        let error = set_network_status_raw(999_999).unwrap_err();
240
241        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
242        assert_eq!(error.raw_status(), Some(sys::MLN_STATUS_INVALID_ARGUMENT));
243        assert!(error.diagnostic().contains("network status"));
244    }
245
246    #[test]
247    fn unknown_network_status_is_rejected_before_calling_c() {
248        let error = set_network_status(NetworkStatus::Unknown(999_999)).unwrap_err();
249
250        assert_eq!(error.kind(), ErrorKind::InvalidArgument);
251        assert_eq!(error.raw_status(), None);
252        assert!(error.diagnostic().contains("cannot be set"));
253    }
254}